def __init__(self, parent, *args): GraphicsPathObject.__init__(self, parent, *args) self.setAcceptHoverEvents(True) self.setPen(QPen(Qt.NoPen)) self.normalBrush = QBrush(QColor("#CDD5D9")) self.connectedBrush = QBrush(QColor("#9CACB4")) self.setBrush(self.normalBrush) self.shadow = QGraphicsDropShadowEffect(blurRadius=10, color=QColor(SHADOW_COLOR), offset=QPointF(0, 0)) self.setGraphicsEffect(self.shadow) self.shadow.setEnabled(False) # Does this item have any anchored links. self.anchored = False if isinstance(parent, NodeItem): self.__parentNodeItem = parent else: self.__parentNodeItem = None self.__anchorPath = QPainterPath() self.__points = [] self.__pointPositions = [] self.__fullStroke = None self.__dottedStroke = None self.__shape = None
def __init__(self, parent=None, line=None, **kwargs): Annotation.__init__(self, parent, **kwargs) self.setFlag(QGraphicsItem.ItemIsMovable) self.setFlag(QGraphicsItem.ItemIsSelectable) self.setFocusPolicy(Qt.ClickFocus) if line is None: line = QLineF(0, 0, 20, 0) self.__line = line self.__color = QColor(Qt.red) self.__arrowItem = ArrowItem(self) self.__arrowItem.setLine(line) self.__arrowItem.setBrush(self.__color) self.__arrowItem.setPen(QPen(Qt.NoPen)) self.__arrowItem.setArrowStyle(ArrowItem.Concave) self.__arrowItem.setLineWidth(5) self.__shadow = QGraphicsDropShadowEffect( blurRadius=5, offset=QPointF(1.0, 2.0), ) self.__arrowItem.setGraphicsEffect(self.__shadow) self.__shadow.setEnabled(True) self.__autoAdjustGeometry = True
def __init__(self, parent): # type: (QGraphicsItem) -> None super().__init__(parent) self.setAcceptedMouseButtons(Qt.NoButton) self.setAcceptHoverEvents(True) self.__animationEnabled = False self.__hover = False self.__enabled = True self.__selected = False self.__shape = None # type: Optional[QPainterPath] self.__curvepath = QPainterPath() self.__curvepath_disabled = None # type: Optional[QPainterPath] self.__pen = self.pen() self.setPen(QPen(QBrush(QColor("#9CACB4")), 2.0)) self.shadow = QGraphicsDropShadowEffect(blurRadius=5, color=QColor(SHADOW_COLOR), offset=QPointF(0, 0)) self.setGraphicsEffect(self.shadow) self.shadow.setEnabled(False) self.__blurAnimation = QPropertyAnimation(self.shadow, b"blurRadius") self.__blurAnimation.setDuration(50) self.__blurAnimation.finished.connect(self.__on_finished)
def __init__(self, parent, **kwargs): # type: (Optional[QGraphicsItem], Any) -> None super().__init__(parent, **kwargs) self.__parentNodeItem = None # type: Optional[NodeItem] self.setAcceptHoverEvents(True) self.setPen(QPen(Qt.NoPen)) self.normalBrush = QBrush(QColor("#CDD5D9")) self.normalHoverBrush = QBrush(QColor("#9CACB4")) self.connectedBrush = self.normalHoverBrush self.connectedHoverBrush = QBrush(QColor("#959595")) self.setBrush(self.normalBrush) self.__animationEnabled = False self.__hover = False # Does this item have any anchored links. self.anchored = False if isinstance(parent, NodeItem): self.__parentNodeItem = parent else: self.__parentNodeItem = None self.__anchorPath = QPainterPath() self.__points = [] # type: List[AnchorPoint] self.__pointPositions = [] # type: List[float] self.__fullStroke = QPainterPath() self.__dottedStroke = QPainterPath() self.__shape = None # type: Optional[QPainterPath] self.shadow = QGraphicsDropShadowEffect( blurRadius=0, color=QColor(SHADOW_COLOR), offset=QPointF(0, 0), ) # self.setGraphicsEffect(self.shadow) self.shadow.setEnabled(False) shadowitem = GraphicsPathObject(self, objectName="shadow-shape-item") shadowitem.setPen(Qt.NoPen) shadowitem.setBrush(QBrush(QColor(SHADOW_COLOR))) shadowitem.setGraphicsEffect(self.shadow) shadowitem.setFlag(QGraphicsItem.ItemStacksBehindParent) self.__shadow = shadowitem self.__blurAnimation = QPropertyAnimation(self.shadow, b"blurRadius", self) self.__blurAnimation.setDuration(50) self.__blurAnimation.finished.connect(self.__on_finished)
def __init__(self, parent=None, line=None, **kwargs): Annotation.__init__(self, parent, **kwargs) self.setFlag(QGraphicsItem.ItemIsMovable) self.setFlag(QGraphicsItem.ItemIsSelectable) self.setFocusPolicy(Qt.ClickFocus) if line is None: line = QLineF(0, 0, 20, 0) self.__line = line self.__color = QColor(Qt.red) self.__arrowItem = ArrowItem(self) self.__arrowItem.setLine(line) self.__arrowItem.setBrush(self.__color) self.__arrowItem.setPen(QPen(Qt.NoPen)) self.__arrowItem.setArrowStyle(ArrowItem.Concave) self.__arrowItem.setLineWidth(5) self.__shadow = QGraphicsDropShadowEffect(blurRadius=5, offset=QPointF(1.0, 2.0)) self.__arrowItem.setGraphicsEffect(self.__shadow) self.__shadow.setEnabled(True) self.__autoAdjustGeometry = True
def __init__(self, parent, *args): GraphicsPathObject.__init__(self, parent, *args) self.setAcceptHoverEvents(True) self.setPen(QPen(Qt.NoPen)) self.normalBrush = QBrush(QColor("#CDD5D9")) self.connectedBrush = QBrush(QColor("#9CACB4")) self.setBrush(self.normalBrush) self.shadow = QGraphicsDropShadowEffect( blurRadius=10, color=QColor(SHADOW_COLOR), offset=QPointF(0, 0) ) self.setGraphicsEffect(self.shadow) self.shadow.setEnabled(False) # Does this item have any anchored links. self.anchored = False if isinstance(parent, NodeItem): self.__parentNodeItem = parent else: self.__parentNodeItem = None self.__anchorPath = QPainterPath() self.__points = [] self.__pointPositions = [] self.__fullStroke = None self.__dottedStroke = None self.__shape = None
def __init__(self, parent=None): # type: (NodeItem) -> None super().__init__(parent) assert isinstance(parent, NodeItem) self.__processingState = 0 self.__progress = -1. self.__animationEnabled = False self.__isSelected = False self.__hasFocus = False self.__hover = False self.__shapeRect = QRectF(-10, -10, 20, 20) self.setAcceptHoverEvents(True) self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges, True) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.setPen(QPen(Qt.NoPen)) self.setPalette(default_palette()) self.shadow = QGraphicsDropShadowEffect( blurRadius=3, color=QColor(SHADOW_COLOR), offset=QPointF(0, 0), ) self.shadow.setEnabled(True) # An item with the same shape as this object, stacked behind this # item as a source for QGraphicsDropShadowEffect. Cannot attach # the effect to this item directly as QGraphicsEffect makes the item # non devicePixelRatio aware. shadowitem = GraphicsPathObject(self, objectName="shadow-shape-item") shadowitem.setPen(Qt.NoPen) shadowitem.setBrush(QBrush(QColor(SHADOW_COLOR).lighter())) shadowitem.setGraphicsEffect(self.shadow) shadowitem.setFlag(QGraphicsItem.ItemStacksBehindParent) self.__shadow = shadowitem self.__blurAnimation = QPropertyAnimation(self.shadow, b"blurRadius", self) self.__blurAnimation.setDuration(100) self.__blurAnimation.finished.connect(self.__on_finished) self.__pingAnimation = QPropertyAnimation(self, b"scale", self) self.__pingAnimation.setDuration(250) self.__pingAnimation.setKeyValues([(0.0, 1.0), (0.5, 1.1), (1.0, 1.0)])
def __init__(self, parent=None, channel=None, rect=None, **kwargs): QGraphicsRectItem.__init__(self, **kwargs) self.setAcceptHoverEvents(True) self.setAcceptedMouseButtons(Qt.NoButton) self.__channel = None if rect is None: rect = QRectF(0, 0, 20, 20) self.setRect(rect) if channel: self.setChannel(channel) self.__shadow = QGraphicsDropShadowEffect(blurRadius=5, offset=QPointF(0, 0)) self.setGraphicsEffect(self.__shadow) self.__shadow.setEnabled(False)
def __init__(self, parent): super().__init__(parent) self.setAcceptedMouseButtons(Qt.NoButton) self.setAcceptHoverEvents(True) self.shadow = QGraphicsDropShadowEffect(blurRadius=10, color=QColor(SHADOW_COLOR), offset=QPointF(0, 0)) self.setGraphicsEffect(self.shadow) self.shadow.setEnabled(False) self.__hover = False self.__enabled = True self.__shape = None self.__curvepath = QPainterPath() self.__curvepath_disabled = None self.__pen = self.pen() self.setPen(QPen(QBrush(QColor("#9CACB4")), 2.0))
def __init__(self, parent=None): super().__init__(parent) self.setAcceptHoverEvents(True) self.__shape = None self.__default_pen = QPen(QColor('#383838'), 4) self.__default_pen.setCapStyle(Qt.RoundCap) self.__hover_pen = QPen(QColor('#000000'), 4) self.__hover_pen.setCapStyle(Qt.RoundCap) self.setPen(self.__default_pen) self.__shadow = QGraphicsDropShadowEffect(blurRadius=10, color=QColor('#9CACB4'), offset=QPointF(0, 0)) self.setGraphicsEffect(self.__shadow) self.prepareGeometryChange() self.__shadow.setEnabled(False)
def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) palette = self.palette() ds = QGraphicsDropShadowEffect(parent=self, objectName="sticky-view-shadow", color=palette.color( QPalette.Foreground), blurRadius=15, offset=QPointF(0, 0), enabled=True) self.setGraphicsEffect(ds)
def __init__(self, parent=None, line=None, **kwargs): # type: (Optional[QGraphicsItem], Optional[QLineF], Any) -> None super().__init__(parent, **kwargs) self.setFlag(QGraphicsItem.ItemIsMovable) self.setFlag(QGraphicsItem.ItemIsSelectable) self.setFocusPolicy(Qt.ClickFocus) if line is None: line = QLineF(0, 0, 20, 0) self.__line = QLineF(line) self.__color = QColor(Qt.red) # An item with the same shape as this arrow, stacked behind this # item as a source for QGraphicsDropShadowEffect. Cannot attach # the effect to this item directly as QGraphicsEffect makes the item # non devicePixelRatio aware. self.__arrowShadowBase = ArrowItem(self, line=line) self.__arrowShadowBase.setPen(Qt.NoPen) # no pen -> slightly thinner self.__arrowShadowBase.setBrush(QBrush(self.__color)) self.__arrowShadowBase.setArrowStyle(ArrowItem.Concave) self.__arrowShadowBase.setLineWidth(5) self.__shadow = QGraphicsDropShadowEffect( blurRadius=5, offset=QPointF(1.0, 2.0), ) self.__arrowShadowBase.setGraphicsEffect(self.__shadow) self.__shadow.setEnabled(True) # The 'real' shape item self.__arrowItem = ArrowItem(self, line=line) self.__arrowItem.setBrush(self.__color) self.__arrowItem.setPen(QPen(self.__color)) self.__arrowItem.setArrowStyle(ArrowItem.Concave) self.__arrowItem.setLineWidth(5) self.__autoAdjustGeometry = True
def __init__(self, parent=None): GraphicsPathObject.__init__(self, parent) assert (isinstance(parent, NodeItem)) self.__processingState = 0 self.__progress = -1 self.__animationEnabled = False self.__isSelected = False self.__hasFocus = False self.__hover = False self.__shapeRect = QRectF(-10, -10, 20, 20) self.setAcceptHoverEvents(True) self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges, True) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.setPen(QPen(Qt.NoPen)) self.setPalette(default_palette()) self.shadow = QGraphicsDropShadowEffect( blurRadius=3, color=QColor(SHADOW_COLOR), offset=QPointF(0, 0), ) self.setGraphicsEffect(self.shadow) self.shadow.setEnabled(True) self.__blurAnimation = QPropertyAnimation(self.shadow, b"blurRadius", self) self.__blurAnimation.setDuration(100) self.__blurAnimation.finished.connect(self.__on_finished) self.__pingAnimation = QPropertyAnimation(self, b"scale", self) self.__pingAnimation.setDuration(250) self.__pingAnimation.setKeyValues([(0.0, 1.0), (0.5, 1.1), (1.0, 1.0)])
class LinkLineItem(QGraphicsLineItem): """ A line connecting two Channel Anchors. """ def __init__(self, parent=None): QGraphicsLineItem.__init__(self, parent) self.setAcceptHoverEvents(True) self.__shape = None self.__default_pen = QPen(QColor('#383838'), 4) self.__default_pen.setCapStyle(Qt.RoundCap) self.__hover_pen = QPen(QColor('#000000'), 4) self.__hover_pen.setCapStyle(Qt.RoundCap) self.setPen(self.__default_pen) self.__shadow = QGraphicsDropShadowEffect( blurRadius=10, color=QColor('#9CACB4'), offset=QPointF(0, 0) ) self.setGraphicsEffect(self.__shadow) self.prepareGeometryChange() self.__shadow.setEnabled(False) def setLine(self, *args, **kwargs): super().setLine(*args, **kwargs) # extends mouse hit area stroke_path = QPainterPathStroker() stroke_path.setCapStyle(Qt.RoundCap) stroke_path.setWidth(10) self.__shape = stroke_path.createStroke(super().shape()) def shape(self): if self.__shape is None: return QGraphicsLineItem.shape(self) return self.__shape def hoverEnterEvent(self, event): self.prepareGeometryChange() self.__shadow.setEnabled(True) self.setPen(self.__hover_pen) self.setZValue(1.0) QGraphicsLineItem.hoverEnterEvent(self, event) def hoverLeaveEvent(self, event): self.prepareGeometryChange() self.__shadow.setEnabled(False) self.setPen(self.__default_pen) self.setZValue(0.0) QGraphicsLineItem.hoverLeaveEvent(self, event)
class LinkLineItem(QGraphicsLineItem): """ A line connecting two Channel Anchors. """ def __init__(self, parent=None): super().__init__(parent) self.setAcceptHoverEvents(True) self.__shape = None self.__default_pen = QPen(QColor('#383838'), 4) self.__default_pen.setCapStyle(Qt.RoundCap) self.__hover_pen = QPen(QColor('#000000'), 4) self.__hover_pen.setCapStyle(Qt.RoundCap) self.setPen(self.__default_pen) self.__shadow = QGraphicsDropShadowEffect( blurRadius=10, color=QColor('#9CACB4'), offset=QPointF(0, 0) ) self.setGraphicsEffect(self.__shadow) self.prepareGeometryChange() self.__shadow.setEnabled(False) def setLine(self, *args, **kwargs): super().setLine(*args, **kwargs) # extends mouse hit area stroke_path = QPainterPathStroker() stroke_path.setCapStyle(Qt.RoundCap) stroke_path.setWidth(10) self.__shape = stroke_path.createStroke(super().shape()) def shape(self): if self.__shape is None: return super().shape() return self.__shape def hoverEnterEvent(self, event): self.prepareGeometryChange() self.__shadow.setEnabled(True) self.setPen(self.__hover_pen) self.setZValue(1.0) super().hoverEnterEvent(event) def hoverLeaveEvent(self, event): self.prepareGeometryChange() self.__shadow.setEnabled(False) self.setPen(self.__default_pen) self.setZValue(0.0) super().hoverLeaveEvent(event)
def __init__(self, parent=None): GraphicsPathObject.__init__(self, parent) assert isinstance(parent, NodeItem) self.__processingState = 0 self.__progress = -1 self.__animationEnabled = False self.__isSelected = False self.__hasFocus = False self.__hover = False self.__shapeRect = QRectF(-10, -10, 20, 20) self.setAcceptHoverEvents(True) self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges, True) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.setPen(QPen(Qt.NoPen)) self.setPalette(default_palette()) self.shadow = QGraphicsDropShadowEffect( blurRadius=3, color=QColor(SHADOW_COLOR), offset=QPointF(0, 0), ) self.shadow.setEnabled(True) # An item with the same shape as this object, stacked behind this # item as a source for QGraphicsDropShadowEffect. Cannot attach # the effect to this item directly as QGraphicsEffect makes the item # non devicePixelRatio aware. shadowitem = GraphicsPathObject(self, objectName="shadow-shape-item") shadowitem.setPen(Qt.NoPen) shadowitem.setBrush(QBrush(QColor(SHADOW_COLOR).lighter())) shadowitem.setGraphicsEffect(self.shadow) shadowitem.setFlag(QGraphicsItem.ItemStacksBehindParent) self.__shadow = shadowitem self.__blurAnimation = QPropertyAnimation(self.shadow, b"blurRadius", self) self.__blurAnimation.setDuration(100) self.__blurAnimation.finished.connect(self.__on_finished) self.__pingAnimation = QPropertyAnimation(self, b"scale", self) self.__pingAnimation.setDuration(250) self.__pingAnimation.setKeyValues([(0.0, 1.0), (0.5, 1.1), (1.0, 1.0)])
def __init__(self, parent=None): QGraphicsLineItem.__init__(self, parent) self.setAcceptHoverEvents(True) self.__shape = None self.__default_pen = QPen(QColor('#383838'), 4) self.__default_pen.setCapStyle(Qt.RoundCap) self.__hover_pen = QPen(QColor('#000000'), 4) self.__hover_pen.setCapStyle(Qt.RoundCap) self.setPen(self.__default_pen) self.__shadow = QGraphicsDropShadowEffect( blurRadius=10, color=QColor('#9CACB4'), offset=QPointF(0, 0) ) self.setGraphicsEffect(self.__shadow) self.prepareGeometryChange() self.__shadow.setEnabled(False)
def __init__(self, parent): super().__init__(parent) self.setAcceptedMouseButtons(Qt.NoButton) self.setAcceptHoverEvents(True) self.shadow = QGraphicsDropShadowEffect( blurRadius=10, color=QColor(SHADOW_COLOR), offset=QPointF(0, 0) ) self.setGraphicsEffect(self.shadow) self.shadow.setEnabled(False) self.__hover = False self.__enabled = True self.__shape = None self.__curvepath = QPainterPath() self.__curvepath_disabled = None self.__pen = self.pen() self.setPen(QPen(QBrush(QColor("#9CACB4")), 2.0))
def render_drop_shadow_frame(pixmap, shadow_rect, shadow_color, offset, radius, rect_fill_color): pixmap.fill(QColor(0, 0, 0, 0)) scene = QGraphicsScene() rect = QGraphicsRectItem(shadow_rect) rect.setBrush(QColor(rect_fill_color)) rect.setPen(QPen(Qt.NoPen)) scene.addItem(rect) effect = QGraphicsDropShadowEffect(color=shadow_color, blurRadius=radius, offset=offset) rect.setGraphicsEffect(effect) scene.setSceneRect(QRectF(QPointF(0, 0), QSizeF(pixmap.size()))) painter = QPainter(pixmap) scene.render(painter) painter.end() scene.clear() scene.deleteLater() return pixmap
class ChannelAnchor(QGraphicsRectItem): """ A rectangular Channel Anchor indicator. """ def __init__(self, parent=None, channel=None, rect=None, **kwargs): QGraphicsRectItem.__init__(self, **kwargs) self.setAcceptHoverEvents(True) self.setAcceptedMouseButtons(Qt.NoButton) self.__channel = None if rect is None: rect = QRectF(0, 0, 20, 20) self.setRect(rect) if channel: self.setChannel(channel) self.__shadow = QGraphicsDropShadowEffect(blurRadius=5, offset=QPointF(0, 0)) self.setGraphicsEffect(self.__shadow) self.__shadow.setEnabled(False) def setChannel(self, channel): """ Set the channel description. """ if channel != self.__channel: self.__channel = channel if hasattr(channel, "description"): self.setToolTip(channel.description) # TODO: Should also include name, type, flags, dynamic in the # tool tip as well as add visual clues to the anchor def channel(self): """ Return the channel description. """ return self.__channel def hoverEnterEvent(self, event): self.__shadow.setEnabled(True) QGraphicsRectItem.hoverEnterEvent(self, event) def hoverLeaveEvent(self, event): self.__shadow.setEnabled(False) QGraphicsRectItem.hoverLeaveEvent(self, event)
def __init__(self, parent=None): GraphicsPathObject.__init__(self, parent) assert(isinstance(parent, NodeItem)) self.__processingState = 0 self.__progress = -1 self.__animationEnabled = False self.__isSelected = False self.__hasFocus = False self.__hover = False self.__shapeRect = QRectF(-10, -10, 20, 20) self.setAcceptHoverEvents(True) self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges, True) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.setPen(QPen(Qt.NoPen)) self.setPalette(default_palette()) self.shadow = QGraphicsDropShadowEffect( blurRadius=3, color=QColor(SHADOW_COLOR), offset=QPointF(0, 0), ) self.setGraphicsEffect(self.shadow) self.shadow.setEnabled(True) self.__blurAnimation = QPropertyAnimation(self.shadow, b"blurRadius", self) self.__blurAnimation.setDuration(100) self.__blurAnimation.finished.connect(self.__on_finished) self.__pingAnimation = QPropertyAnimation(self, b"scale", self) self.__pingAnimation.setDuration(250) self.__pingAnimation.setKeyValues([(0.0, 1.0), (0.5, 1.1), (1.0, 1.0)])
def __init__(self, parent=None, line=None, **kwargs): Annotation.__init__(self, parent, **kwargs) self.setFlag(QGraphicsItem.ItemIsMovable) self.setFlag(QGraphicsItem.ItemIsSelectable) self.setFocusPolicy(Qt.ClickFocus) if line is None: line = QLineF(0, 0, 20, 0) self.__line = QLineF(line) self.__color = QColor(Qt.red) # An item with the same shape as this arrow, stacked behind this # item as a source for QGraphicsDropShadowEffect. Cannot attach # the effect to this item directly as QGraphicsEffect makes the item # non devicePixelRatio aware. self.__arrowShadowBase = ArrowItem(self, line=line) self.__arrowShadowBase.setPen(Qt.NoPen) # no pen -> slightly thinner self.__arrowShadowBase.setBrush(QBrush(self.__color)) self.__arrowShadowBase.setArrowStyle(ArrowItem.Concave) self.__arrowShadowBase.setLineWidth(5) self.__shadow = QGraphicsDropShadowEffect( blurRadius=5, offset=QPointF(1.0, 2.0), ) self.__arrowShadowBase.setGraphicsEffect(self.__shadow) self.__shadow.setEnabled(True) # The 'real' shape item self.__arrowItem = ArrowItem(self, line=line) self.__arrowItem.setBrush(self.__color) self.__arrowItem.setPen(QPen(self.__color)) self.__arrowItem.setArrowStyle(ArrowItem.Concave) self.__arrowItem.setLineWidth(5) self.__autoAdjustGeometry = True
class LinkCurveItem(QGraphicsPathItem): """ Link curve item. The main component of a :class:`LinkItem`. """ def __init__(self, parent): super(LinkCurveItem, self).__init__(parent) self.setAcceptedMouseButtons(Qt.NoButton) self.setAcceptHoverEvents(True) self.shadow = QGraphicsDropShadowEffect( blurRadius=5, color=QColor(SHADOW_COLOR), offset=QPointF(0, 0) ) self.setGraphicsEffect(self.shadow) self.shadow.setEnabled(False) self.__hover = False self.__enabled = True self.__shape = None self.__curvepath = QPainterPath() self.__curvepath_disabled = None self.__pen = self.pen() self.setPen(QPen(QBrush(QColor("#9CACB4")), 2.0)) def setCurvePath(self, path): if path != self.__curvepath: self.prepareGeometryChange() self.__curvepath = QPainterPath(path) self.__curvepath_disabled = None self.__shape = None self.__update() def curvePath(self): return QPainterPath(self.__curvepath) def setHoverState(self, state): self.prepareGeometryChange() self.__hover = state self.__update() def setLinkEnabled(self, state): self.prepareGeometryChange() self.__enabled = state self.__update() def isLinkEnabled(self): return self.__enabled def setPen(self, pen): if self.__pen != pen: self.prepareGeometryChange() self.__pen = QPen(pen) self.__shape = None super(LinkCurveItem, self).setPen(self.__pen) def shape(self): if self.__shape is None: path = self.curvePath() pen = QPen(self.pen()) pen.setWidthF(max(pen.widthF(), 7.0)) pen.setStyle(Qt.SolidLine) self.__shape = stroke_path(path, pen) return self.__shape def setPath(self, path): self.__shape = None super(LinkCurveItem, self).setPath(path) def __update(self): shadow_enabled = self.__hover if self.shadow.isEnabled() != shadow_enabled: self.shadow.setEnabled(shadow_enabled) basecurve = self.__curvepath link_enabled = self.__enabled if link_enabled: path = basecurve else: if self.__curvepath_disabled is None: self.__curvepath_disabled = path_link_disabled(basecurve) path = self.__curvepath_disabled self.setPath(path)
class ArrowAnnotation(Annotation): def __init__(self, parent=None, line=None, **kwargs): # type: (Optional[QGraphicsItem], Optional[QLineF], Any) -> None super().__init__(parent, **kwargs) self.setFlag(QGraphicsItem.ItemIsMovable) self.setFlag(QGraphicsItem.ItemIsSelectable) self.setFocusPolicy(Qt.ClickFocus) if line is None: line = QLineF(0, 0, 20, 0) self.__line = QLineF(line) self.__color = QColor(Qt.red) # An item with the same shape as this arrow, stacked behind this # item as a source for QGraphicsDropShadowEffect. Cannot attach # the effect to this item directly as QGraphicsEffect makes the item # non devicePixelRatio aware. self.__arrowShadowBase = ArrowItem(self, line=line) self.__arrowShadowBase.setPen(Qt.NoPen) # no pen -> slightly thinner self.__arrowShadowBase.setBrush(QBrush(self.__color)) self.__arrowShadowBase.setArrowStyle(ArrowItem.Concave) self.__arrowShadowBase.setLineWidth(5) self.__shadow = QGraphicsDropShadowEffect( blurRadius=5, offset=QPointF(1.0, 2.0), ) self.__arrowShadowBase.setGraphicsEffect(self.__shadow) self.__shadow.setEnabled(True) # The 'real' shape item self.__arrowItem = ArrowItem(self, line=line) self.__arrowItem.setBrush(self.__color) self.__arrowItem.setPen(QPen(self.__color)) self.__arrowItem.setArrowStyle(ArrowItem.Concave) self.__arrowItem.setLineWidth(5) self.__autoAdjustGeometry = True def setAutoAdjustGeometry(self, autoAdjust): # type: (bool) -> None """ If set to `True` then the geometry will be adjusted whenever the arrow is changed with `setLine`. Otherwise the geometry of the item is only updated so the `line` lies within the `geometry()` rect (i.e. it only grows). True by default """ self.__autoAdjustGeometry = autoAdjust if autoAdjust: self.adjustGeometry() def autoAdjustGeometry(self): # type: () -> bool """ Should the geometry of the item be adjusted automatically when `setLine` is called. """ return self.__autoAdjustGeometry 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 line(self): # type: () -> QLineF """ Return the arrow base line (`QLineF` in object coordinates). """ return QLineF(self.__line) def setColor(self, color): # type: (QColor) -> None """ Set arrow brush color. """ if self.__color != color: self.__color = QColor(color) self.__updateStyleState() def color(self): # type: () -> QColor """ Return the arrow brush color. """ return QColor(self.__color) def setLineWidth(self, lineWidth): # type: (float) -> None """ Set the arrow line width. """ self.__arrowItem.setLineWidth(lineWidth) self.__arrowShadowBase.setLineWidth(lineWidth) def lineWidth(self): # type: () -> float """ Return the arrow line width. """ return self.__arrowItem.lineWidth() 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 shape(self): # type: () -> QPainterPath arrow_shape = self.__arrowItem.shape() return self.mapFromItem(self.__arrowItem, arrow_shape) def itemChange(self, change, value): # type: (QGraphicsItem.GraphicsItemChange, Any) -> Any if change == QGraphicsItem.ItemSelectedHasChanged: self.__updateStyleState() return super().itemChange(change, value) def __updateStyleState(self): # type: () -> None """ Update the arrows' brush, pen, ... based on it's state """ if self.isSelected(): color = self.__color.darker(150) pen = QPen(QColor(96, 158, 215), Qt.DashDotLine) pen.setWidthF(1.25) pen.setCosmetic(True) shadow = pen.color().darker(150) else: color = self.__color pen = QPen(color) shadow = QColor(63, 63, 63, 180) self.__arrowShadowBase.setBrush(color) self.__shadow.setColor(shadow) self.__arrowItem.setBrush(color) self.__arrowItem.setPen(pen)
class LinkCurveItem(QGraphicsPathItem): """ Link curve item. The main component of a :class:`LinkItem`. """ def __init__(self, parent): # type: (QGraphicsItem) -> None super().__init__(parent) self.setAcceptedMouseButtons(Qt.NoButton) self.setAcceptHoverEvents(True) self.__animationEnabled = False self.__hover = False self.__enabled = True self.__shape = None # type: Optional[QPainterPath] self.__curvepath = QPainterPath() self.__curvepath_disabled = None # type: Optional[QPainterPath] self.__pen = self.pen() self.setPen(QPen(QBrush(QColor("#9CACB4")), 2.0)) self.shadow = QGraphicsDropShadowEffect(blurRadius=5, color=QColor(SHADOW_COLOR), offset=QPointF(0, 0)) self.setGraphicsEffect(self.shadow) self.shadow.setEnabled(False) self.__blurAnimation = QPropertyAnimation(self.shadow, b"blurRadius") self.__blurAnimation.setDuration(50) self.__blurAnimation.finished.connect(self.__on_finished) def setCurvePath(self, path): # type: (QPainterPath) -> None if path != self.__curvepath: self.prepareGeometryChange() self.__curvepath = QPainterPath(path) self.__curvepath_disabled = None self.__shape = None self.__update() def curvePath(self): # type: () -> QPainterPath return QPainterPath(self.__curvepath) def setHoverState(self, state): # type: (bool) -> None self.prepareGeometryChange() self.__hover = state self.__update() def setLinkEnabled(self, state): # type: (bool) -> None self.prepareGeometryChange() self.__enabled = state self.__update() def isLinkEnabled(self): # type: () -> bool return self.__enabled def setPen(self, pen): # type: (QPen) -> None if self.__pen != pen: self.prepareGeometryChange() self.__pen = QPen(pen) self.__shape = None super().setPen(self.__pen) def shape(self): # type: () -> QPainterPath if self.__shape is None: path = self.curvePath() pen = QPen(self.pen()) pen.setWidthF(max(pen.widthF(), 25.0)) pen.setStyle(Qt.SolidLine) self.__shape = stroke_path(path, pen) return self.__shape def setPath(self, path): # type: (QPainterPath) -> None self.__shape = None super().setPath(path) def setAnimationEnabled(self, enabled): # type: (bool) -> None """ Set the link item animation enabled. """ if self.__animationEnabled != enabled: self.__animationEnabled = enabled def __update(self): # type: () -> None radius = 5 if self.__hover else 0 if radius != 0 and not self.shadow.isEnabled(): self.shadow.setEnabled(True) if self.__animationEnabled: if self.__blurAnimation.state() == QPropertyAnimation.Running: self.__blurAnimation.pause() self.__blurAnimation.setStartValue(self.shadow.blurRadius()) self.__blurAnimation.setEndValue(radius) self.__blurAnimation.start() else: self.shadow.setBlurRadius(radius) basecurve = self.__curvepath link_enabled = self.__enabled if link_enabled: path = basecurve else: if self.__curvepath_disabled is None: self.__curvepath_disabled = path_link_disabled(basecurve) path = self.__curvepath_disabled self.setPath(path) def __on_finished(self): if self.shadow.blurRadius() == 0: self.shadow.setEnabled(False)
class NodeBodyItem(GraphicsPathObject): """ The central part (body) of the `NodeItem`. """ def __init__(self, parent=None): GraphicsPathObject.__init__(self, parent) assert (isinstance(parent, NodeItem)) self.__processingState = 0 self.__progress = -1 self.__animationEnabled = False self.__isSelected = False self.__hasFocus = False self.__hover = False self.__shapeRect = QRectF(-10, -10, 20, 20) self.setAcceptHoverEvents(True) self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges, True) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.setPen(QPen(Qt.NoPen)) self.setPalette(default_palette()) self.shadow = QGraphicsDropShadowEffect( blurRadius=3, color=QColor(SHADOW_COLOR), offset=QPointF(0, 0), ) self.shadow.setEnabled(True) # An item with the same shape as this object, stacked behind this # item as a source for QGraphicsDropShadowEffect. Cannot attach # the effect to this item directly as QGraphicsEffect makes the item # non devicePixelRatio aware. shadowitem = GraphicsPathObject(self, objectName="shadow-shape-item") shadowitem.setPen(Qt.NoPen) shadowitem.setBrush(QBrush(QColor(SHADOW_COLOR).lighter())) shadowitem.setGraphicsEffect(self.shadow) shadowitem.setFlag(QGraphicsItem.ItemStacksBehindParent) self.__shadow = shadowitem self.__blurAnimation = QPropertyAnimation(self.shadow, b"blurRadius", self) self.__blurAnimation.setDuration(100) self.__blurAnimation.finished.connect(self.__on_finished) self.__pingAnimation = QPropertyAnimation(self, b"scale", self) self.__pingAnimation.setDuration(250) self.__pingAnimation.setKeyValues([(0.0, 1.0), (0.5, 1.1), (1.0, 1.0)]) # TODO: The body item should allow the setting of arbitrary painter # paths (for instance rounded rect, ...) def setShapeRect(self, rect): """ Set the item's shape `rect`. The item should be confined within this rect. """ path = QPainterPath() path.addEllipse(rect) self.setPath(path) self.__shadow.setPath(path) self.__shapeRect = rect def setPalette(self, palette): """ Set the body color palette (:class:`QPalette`). """ self.palette = palette self.__updateBrush() def setAnimationEnabled(self, enabled): """ Set the node animation enabled. """ if self.__animationEnabled != enabled: self.__animationEnabled = enabled def setProcessingState(self, state): """ Set the processing state of the node. """ if self.__processingState != state: self.__processingState = state if not state and self.__animationEnabled: self.ping() def setProgress(self, progress): """ Set the progress indicator state of the node. `progress` should be a number between 0 and 100. """ self.__progress = progress self.update() def ping(self): """ Trigger a 'ping' animation. """ animation_restart(self.__pingAnimation) def hoverEnterEvent(self, event): self.__hover = True self.__updateShadowState() return GraphicsPathObject.hoverEnterEvent(self, event) def hoverLeaveEvent(self, event): self.__hover = False self.__updateShadowState() return GraphicsPathObject.hoverLeaveEvent(self, event) def paint(self, painter, option, widget): """ Paint the shape and a progress meter. """ # Let the default implementation draw the shape if option.state & QStyle.State_Selected: # Prevent the default bounding rect selection indicator. option.state = option.state ^ QStyle.State_Selected GraphicsPathObject.paint(self, painter, option, widget) if self.__progress >= 0: # Draw the progress meter over the shape. # Set the clip to shape so the meter does not overflow the shape. painter.save() painter.setClipPath(self.shape(), Qt.ReplaceClip) color = self.palette.color(QPalette.ButtonText) pen = QPen(color, 5) painter.setPen(pen) painter.setRenderHints(QPainter.Antialiasing) span = max(1, int(self.__progress * 57.60)) painter.drawArc(self.__shapeRect, 90 * 16, -span) painter.restore() def __updateShadowState(self): if self.__hasFocus: color = QColor(FOCUS_OUTLINE_COLOR) self.setPen(QPen(color, 1.5)) else: self.setPen(QPen(Qt.NoPen)) radius = 3 enabled = False if self.__isSelected: enabled = True radius = 7 if self.__hover: radius = 17 enabled = True if enabled and not self.shadow.isEnabled(): self.shadow.setEnabled(enabled) if self.__animationEnabled: if self.__blurAnimation.state() == QPropertyAnimation.Running: self.__blurAnimation.pause() self.__blurAnimation.setStartValue(self.shadow.blurRadius()) self.__blurAnimation.setEndValue(radius) self.__blurAnimation.start() else: self.shadow.setBlurRadius(radius) def __updateBrush(self): palette = self.palette if self.__isSelected: cg = QPalette.Active else: cg = QPalette.Inactive palette.setCurrentColorGroup(cg) c1 = palette.color(QPalette.Light) c2 = palette.color(QPalette.Button) grad = radial_gradient(c2, c1) self.setBrush(QBrush(grad)) # TODO: The selected and focus states should be set using the # QStyle flags (State_Selected. State_HasFocus) def setSelected(self, selected): """ Set the `selected` state. .. note:: The item does not have `QGraphicsItem.ItemIsSelectable` flag. This property is instead controlled by the parent NodeItem. """ self.__isSelected = selected self.__updateBrush() def setHasFocus(self, focus): """ Set the `has focus` state. .. note:: The item does not have `QGraphicsItem.ItemIsFocusable` flag. This property is instead controlled by the parent NodeItem. """ self.__hasFocus = focus self.__updateShadowState() def __on_finished(self): if self.shadow.blurRadius() == 0: self.shadow.setEnabled(False)
class NodeAnchorItem(GraphicsPathObject): """ The left/right widget input/output anchors. """ def __init__(self, parent, **kwargs): # type: (Optional[QGraphicsItem], Any) -> None super().__init__(parent, **kwargs) self.__parentNodeItem = None # type: Optional[NodeItem] self.setAcceptHoverEvents(True) self.setPen(QPen(Qt.NoPen)) self.normalBrush = QBrush(QColor("#CDD5D9")) self.normalHoverBrush = QBrush(QColor("#9CACB4")) self.connectedBrush = self.normalHoverBrush self.connectedHoverBrush = QBrush(QColor("#959595")) self.setBrush(self.normalBrush) self.__animationEnabled = False self.__hover = False # Does this item have any anchored links. self.anchored = False if isinstance(parent, NodeItem): self.__parentNodeItem = parent else: self.__parentNodeItem = None self.__anchorPath = QPainterPath() self.__points = [] # type: List[AnchorPoint] self.__pointPositions = [] # type: List[float] self.__fullStroke = QPainterPath() self.__dottedStroke = QPainterPath() self.__shape = None # type: Optional[QPainterPath] self.shadow = QGraphicsDropShadowEffect( blurRadius=0, color=QColor(SHADOW_COLOR), offset=QPointF(0, 0), ) # self.setGraphicsEffect(self.shadow) self.shadow.setEnabled(False) shadowitem = GraphicsPathObject(self, objectName="shadow-shape-item") shadowitem.setPen(Qt.NoPen) shadowitem.setBrush(QBrush(QColor(SHADOW_COLOR))) shadowitem.setGraphicsEffect(self.shadow) shadowitem.setFlag(QGraphicsItem.ItemStacksBehindParent) self.__shadow = shadowitem self.__blurAnimation = QPropertyAnimation(self.shadow, b"blurRadius", self) self.__blurAnimation.setDuration(50) self.__blurAnimation.finished.connect(self.__on_finished) def parentNodeItem(self): # type: () -> Optional['NodeItem'] """ Return a parent :class:`NodeItem` or ``None`` if this anchor's parent is not a :class:`NodeItem` instance. """ return self.__parentNodeItem def setAnchorPath(self, path): # type: (QPainterPath) -> None """ Set the anchor's curve path as a :class:`QPainterPath`. """ self.__anchorPath = QPainterPath(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(25) self.prepareGeometryChange() 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: assert self.__fullStroke is not None self.setPath(self.__fullStroke) self.__shadow.setPath(self.__fullStroke) brush = self.connectedHoverBrush if self.__hover else self.connectedBrush self.setBrush(brush) else: assert self.__dottedStroke is not None self.setPath(self.__dottedStroke) self.__shadow.setPath(self.__dottedStroke) brush = self.normalHoverBrush if self.__hover else self.normalBrush self.setBrush(brush) def anchorPath(self): # type: () -> QPainterPath """ Return the anchor path (:class:`QPainterPath`). This is a curve on which the anchor points lie. """ return QPainterPath(self.__anchorPath) def setAnchored(self, anchored): # type: (bool) -> None """ Set the items anchored state. When ``False`` the item draws it self with a dotted stroke. """ self.anchored = anchored if anchored: self.setPath(self.__fullStroke) self.__shadow.setPath(self.__fullStroke) hover = self.__hover and len( self.__points) > 1 # a stylistic choice brush = self.connectedHoverBrush if hover else self.connectedBrush self.setBrush(brush) else: self.setPath(self.__dottedStroke) self.__shadow.setPath(self.__dottedStroke) brush = self.normalHoverBrush if self.__hover else self.normalBrush self.setBrush(brush) def setConnectionHint(self, hint=None): """ Set the connection hint. This can be used to indicate if a connection can be made or not. """ raise NotImplementedError def count(self): # type: () -> int """ Return the number of anchor points. """ return len(self.__points) def addAnchor(self, anchor, position=0.5): # type: (AnchorPoint, float) -> int """ Add a new :class:`AnchorPoint` to this item and return it's index. The `position` specifies where along the `anchorPath` is the new point inserted. """ return self.insertAnchor(self.count(), anchor, position) def insertAnchor(self, index, anchor, position=0.5): # type: (int, AnchorPoint, float) -> int """ Insert a new :class:`AnchorPoint` at `index`. See also -------- NodeAnchorItem.addAnchor """ if anchor in self.__points: raise ValueError("%s already added." % anchor) self.__points.insert(index, anchor) self.__pointPositions.insert(index, position) anchor.setParentItem(self) anchor.setPos(self.__anchorPath.pointAtPercent(position)) anchor.destroyed.connect(self.__onAnchorDestroyed) self.__updatePositions() self.setAnchored(bool(self.__points)) hover = self.__hover and len(self.__points) > 1 # a stylistic choice anchor.setHoverState(hover) return index def removeAnchor(self, anchor): # type: (AnchorPoint) -> None """ Remove and delete the anchor point. """ anchor = self.takeAnchor(anchor) anchor.hide() anchor.setParentItem(None) anchor.deleteLater() def takeAnchor(self, anchor): # type: (AnchorPoint) -> AnchorPoint """ Remove the anchor but don't delete it. """ index = self.__points.index(anchor) del self.__points[index] del self.__pointPositions[index] anchor.destroyed.disconnect(self.__onAnchorDestroyed) self.__updatePositions() self.setAnchored(bool(self.__points)) return anchor def __onAnchorDestroyed(self, anchor): # type: (QObject) -> None try: index = self.__points.index(anchor) except ValueError: return del self.__points[index] del self.__pointPositions[index] def anchorPoints(self): # type: () -> List[AnchorPoint] """ Return a list of anchor points. """ return list(self.__points) def anchorPoint(self, index): # type: (int) -> AnchorPoint """ Return the anchor point at `index`. """ return self.__points[index] def setAnchorPositions(self, positions): # type: (Iterable[float]) -> None """ Set the anchor positions in percentages (0..1) along the path curve. """ if self.__pointPositions != positions: self.__pointPositions = list(positions) self.__updatePositions() def anchorPositions(self): # type: () -> List[float] """ Return the positions of anchor points as a list of floats where each float is between 0 and 1 and specifies where along the anchor path does the point lie (0 is at start 1 is at the end). """ return list(self.__pointPositions) def shape(self): # type: () -> QPainterPath if self.__shape is not None: return QPainterPath(self.__shape) else: return super().shape() def boundingRect(self): if self.__shape is not None: return self.__shape.controlPointRect() else: return GraphicsPathObject.boundingRect(self) def hoverEnterEvent(self, event): self.__hover = True brush = self.connectedHoverBrush if self.anchored else self.normalHoverBrush self.setBrush(brush) self.__updateShadowState() return super().hoverEnterEvent(event) def hoverLeaveEvent(self, event): self.__hover = False brush = self.connectedBrush if self.anchored else self.normalBrush self.setBrush(brush) self.__updateShadowState() return super().hoverLeaveEvent(event) def setAnimationEnabled(self, enabled): # type: (bool) -> None """ Set the anchor animation enabled. """ if self.__animationEnabled != enabled: self.__animationEnabled = enabled def __updateShadowState(self): # type: () -> None radius = 5 if self.__hover else 0 if radius != 0 and not self.shadow.isEnabled(): self.shadow.setEnabled(True) if self.__animationEnabled: if self.__blurAnimation.state() == QPropertyAnimation.Running: self.__blurAnimation.pause() self.__blurAnimation.setStartValue(self.shadow.blurRadius()) self.__blurAnimation.setEndValue(radius) self.__blurAnimation.start() else: self.shadow.setBlurRadius(radius) for anchor in self.anchorPoints(): anchor.setHoverState(self.__hover) def __updatePositions(self): # type: () -> None """Update anchor points positions. """ for point, t in zip(self.__points, self.__pointPositions): pos = self.__anchorPath.pointAtPercent(t) point.setPos(pos) def __on_finished(self): # type: () -> None if self.shadow.blurRadius() == 0: self.shadow.setEnabled(False)
class NodeAnchorItem(GraphicsPathObject): """ The left/right widget input/output anchors. """ def __init__(self, parent, *args): GraphicsPathObject.__init__(self, parent, *args) self.setAcceptHoverEvents(True) self.setPen(QPen(Qt.NoPen)) self.normalBrush = QBrush(QColor("#CDD5D9")) self.connectedBrush = QBrush(QColor("#9CACB4")) self.setBrush(self.normalBrush) self.shadow = QGraphicsDropShadowEffect(blurRadius=10, color=QColor(SHADOW_COLOR), offset=QPointF(0, 0)) self.setGraphicsEffect(self.shadow) self.shadow.setEnabled(False) # Does this item have any anchored links. self.anchored = False if isinstance(parent, NodeItem): self.__parentNodeItem = parent else: self.__parentNodeItem = None self.__anchorPath = QPainterPath() self.__points = [] self.__pointPositions = [] self.__fullStroke = None self.__dottedStroke = None self.__shape = None def parentNodeItem(self): """ Return a parent :class:`NodeItem` or ``None`` if this anchor's parent is not a :class:`NodeItem` instance. """ return self.__parentNodeItem 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) def anchorPath(self): """ Return the anchor path (:class:`QPainterPath`). This is a curve on which the anchor points lie. """ return self.__anchorPath def setAnchored(self, anchored): """ Set the items anchored state. When ``False`` the item draws it self with a dotted stroke. """ self.anchored = anchored if anchored: self.setPath(self.__fullStroke) self.setBrush(self.connectedBrush) else: self.setPath(self.__dottedStroke) self.setBrush(self.normalBrush) def setConnectionHint(self, hint=None): """ Set the connection hint. This can be used to indicate if a connection can be made or not. """ raise NotImplementedError def count(self): """ Return the number of anchor points. """ return len(self.__points) def addAnchor(self, anchor, position=0.5): """ Add a new :class:`AnchorPoint` to this item and return it's index. The `position` specifies where along the `anchorPath` is the new point inserted. """ return self.insertAnchor(self.count(), anchor, position) def insertAnchor(self, index, anchor, position=0.5): """ Insert a new :class:`AnchorPoint` at `index`. See also -------- NodeAnchorItem.addAnchor """ if anchor in self.__points: raise ValueError("%s already added." % anchor) self.__points.insert(index, anchor) self.__pointPositions.insert(index, position) anchor.setParentItem(self) anchor.setPos(self.__anchorPath.pointAtPercent(position)) anchor.destroyed.connect(self.__onAnchorDestroyed) self.__updatePositions() self.setAnchored(bool(self.__points)) return index def removeAnchor(self, anchor): """ Remove and delete the anchor point. """ anchor = self.takeAnchor(anchor) anchor.hide() anchor.setParentItem(None) anchor.deleteLater() def takeAnchor(self, anchor): """ Remove the anchor but don't delete it. """ index = self.__points.index(anchor) del self.__points[index] del self.__pointPositions[index] anchor.destroyed.disconnect(self.__onAnchorDestroyed) self.__updatePositions() self.setAnchored(bool(self.__points)) return anchor def __onAnchorDestroyed(self, anchor): try: index = self.__points.index(anchor) except ValueError: return del self.__points[index] del self.__pointPositions[index] def anchorPoints(self): """ Return a list of anchor points. """ return list(self.__points) def anchorPoint(self, index): """ Return the anchor point at `index`. """ return self.__points[index] def setAnchorPositions(self, positions): """ Set the anchor positions in percentages (0..1) along the path curve. """ if self.__pointPositions != positions: self.__pointPositions = list(positions) self.__updatePositions() def anchorPositions(self): """ Return the positions of anchor points as a list of floats where each float is between 0 and 1 and specifies where along the anchor path does the point lie (0 is at start 1 is at the end). """ return list(self.__pointPositions) def shape(self): if self.__shape is not None: return self.__shape else: return GraphicsPathObject.shape(self) def hoverEnterEvent(self, event): self.shadow.setEnabled(True) return GraphicsPathObject.hoverEnterEvent(self, event) def hoverLeaveEvent(self, event): self.shadow.setEnabled(False) return GraphicsPathObject.hoverLeaveEvent(self, event) def __updatePositions(self): """Update anchor points positions. """ for point, t in zip(self.__points, self.__pointPositions): pos = self.__anchorPath.pointAtPercent(t) point.setPos(pos)
class NodeBodyItem(GraphicsPathObject): """ The central part (body) of the `NodeItem`. """ def __init__(self, parent=None): GraphicsPathObject.__init__(self, parent) assert isinstance(parent, NodeItem) self.__processingState = 0 self.__progress = -1 self.__animationEnabled = False self.__isSelected = False self.__hasFocus = False self.__hover = False self.__shapeRect = QRectF(-10, -10, 20, 20) self.setAcceptHoverEvents(True) self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges, True) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.setPen(QPen(Qt.NoPen)) self.setPalette(default_palette()) self.shadow = QGraphicsDropShadowEffect( blurRadius=3, color=QColor(SHADOW_COLOR), offset=QPointF(0, 0), ) self.shadow.setEnabled(True) # An item with the same shape as this object, stacked behind this # item as a source for QGraphicsDropShadowEffect. Cannot attach # the effect to this item directly as QGraphicsEffect makes the item # non devicePixelRatio aware. shadowitem = GraphicsPathObject(self, objectName="shadow-shape-item") shadowitem.setPen(Qt.NoPen) shadowitem.setBrush(QBrush(QColor(SHADOW_COLOR).lighter())) shadowitem.setGraphicsEffect(self.shadow) shadowitem.setFlag(QGraphicsItem.ItemStacksBehindParent) self.__shadow = shadowitem self.__blurAnimation = QPropertyAnimation(self.shadow, b"blurRadius", self) self.__blurAnimation.setDuration(100) self.__blurAnimation.finished.connect(self.__on_finished) self.__pingAnimation = QPropertyAnimation(self, b"scale", self) self.__pingAnimation.setDuration(250) self.__pingAnimation.setKeyValues([(0.0, 1.0), (0.5, 1.1), (1.0, 1.0)]) # TODO: The body item should allow the setting of arbitrary painter # paths (for instance rounded rect, ...) def setShapeRect(self, rect): """ Set the item's shape `rect`. The item should be confined within this rect. """ path = QPainterPath() path.addEllipse(rect) self.setPath(path) self.__shadow.setPath(path) self.__shapeRect = rect def setPalette(self, palette): """ Set the body color palette (:class:`QPalette`). """ self.palette = palette self.__updateBrush() def setAnimationEnabled(self, enabled): """ Set the node animation enabled. """ if self.__animationEnabled != enabled: self.__animationEnabled = enabled def setProcessingState(self, state): """ Set the processing state of the node. """ if self.__processingState != state: self.__processingState = state if not state and self.__animationEnabled: self.ping() def setProgress(self, progress): """ Set the progress indicator state of the node. `progress` should be a number between 0 and 100. """ self.__progress = progress self.update() def ping(self): """ Trigger a 'ping' animation. """ animation_restart(self.__pingAnimation) def hoverEnterEvent(self, event): self.__hover = True self.__updateShadowState() return GraphicsPathObject.hoverEnterEvent(self, event) def hoverLeaveEvent(self, event): self.__hover = False self.__updateShadowState() return GraphicsPathObject.hoverLeaveEvent(self, event) def paint(self, painter, option, widget): """ Paint the shape and a progress meter. """ # Let the default implementation draw the shape if option.state & QStyle.State_Selected: # Prevent the default bounding rect selection indicator. option.state = option.state ^ QStyle.State_Selected GraphicsPathObject.paint(self, painter, option, widget) if self.__progress >= 0: # Draw the progress meter over the shape. # Set the clip to shape so the meter does not overflow the shape. painter.save() painter.setClipPath(self.shape(), Qt.ReplaceClip) color = self.palette.color(QPalette.ButtonText) pen = QPen(color, 5) painter.setPen(pen) painter.setRenderHints(QPainter.Antialiasing) span = max(1, int(self.__progress * 57.60)) painter.drawArc(self.__shapeRect, 90 * 16, -span) painter.restore() def __updateShadowState(self): if self.__hasFocus: color = QColor(FOCUS_OUTLINE_COLOR) self.setPen(QPen(color, 1.5)) else: self.setPen(QPen(Qt.NoPen)) radius = 3 enabled = False if self.__isSelected: enabled = True radius = 7 if self.__hover: radius = 17 enabled = True if enabled and not self.shadow.isEnabled(): self.shadow.setEnabled(enabled) if self.__animationEnabled: if self.__blurAnimation.state() == QPropertyAnimation.Running: self.__blurAnimation.pause() self.__blurAnimation.setStartValue(self.shadow.blurRadius()) self.__blurAnimation.setEndValue(radius) self.__blurAnimation.start() else: self.shadow.setBlurRadius(radius) def __updateBrush(self): palette = self.palette if self.__isSelected: cg = QPalette.Active else: cg = QPalette.Inactive palette.setCurrentColorGroup(cg) c1 = palette.color(QPalette.Light) c2 = palette.color(QPalette.Button) grad = radial_gradient(c2, c1) self.setBrush(QBrush(grad)) # TODO: The selected and focus states should be set using the # QStyle flags (State_Selected. State_HasFocus) def setSelected(self, selected): """ Set the `selected` state. .. note:: The item does not have `QGraphicsItem.ItemIsSelectable` flag. This property is instead controlled by the parent NodeItem. """ self.__isSelected = selected self.__updateBrush() def setHasFocus(self, focus): """ Set the `has focus` state. .. note:: The item does not have `QGraphicsItem.ItemIsFocusable` flag. This property is instead controlled by the parent NodeItem. """ self.__hasFocus = focus self.__updateShadowState() def __on_finished(self): if self.shadow.blurRadius() == 0: self.shadow.setEnabled(False)
class LinkCurveItem(QGraphicsPathItem): """ Link curve item. The main component of a :class:`LinkItem`. """ def __init__(self, parent): super().__init__(parent) self.setAcceptedMouseButtons(Qt.NoButton) self.setAcceptHoverEvents(True) self.shadow = QGraphicsDropShadowEffect( blurRadius=10, color=QColor(SHADOW_COLOR), offset=QPointF(0, 0) ) self.setGraphicsEffect(self.shadow) self.shadow.setEnabled(False) self.__hover = False self.__enabled = True self.__shape = None self.__curvepath = QPainterPath() self.__curvepath_disabled = None self.__pen = self.pen() self.setPen(QPen(QBrush(QColor("#9CACB4")), 2.0)) def setCurvePath(self, path): if path != self.__curvepath: self.prepareGeometryChange() self.__curvepath = QPainterPath(path) self.__curvepath_disabled = None self.__shape = None self.__update() def curvePath(self): return QPainterPath(self.__curvepath) def setHoverState(self, state): self.prepareGeometryChange() self.__hover = state self.__update() def setLinkEnabled(self, state): self.prepareGeometryChange() self.__enabled = state self.__update() def isLinkEnabled(self): return self.__enabled def setPen(self, pen): if self.__pen != pen: self.prepareGeometryChange() self.__pen = QPen(pen) self.__shape = None super().setPen(self.__pen) def shape(self): if self.__shape is None: path = self.curvePath() pen = QPen(QBrush(Qt.black), max(self.pen().widthF(), 20), Qt.SolidLine) self.__shape = stroke_path(path, pen) return self.__shape def setPath(self, path): self.__shape = None super().setPath(path) def __update(self): shadow_enabled = self.__hover if self.shadow.isEnabled() != shadow_enabled: self.shadow.setEnabled(shadow_enabled) basecurve = self.__curvepath link_enabled = self.__enabled if link_enabled: path = basecurve else: if self.__curvepath_disabled is None: self.__curvepath_disabled = path_link_disabled(basecurve) path = self.__curvepath_disabled self.setPath(path)
class NodeAnchorItem(GraphicsPathObject): """ The left/right widget input/output anchors. """ def __init__(self, parent, *args): GraphicsPathObject.__init__(self, parent, *args) self.setAcceptHoverEvents(True) self.setPen(QPen(Qt.NoPen)) self.normalBrush = QBrush(QColor("#CDD5D9")) self.connectedBrush = QBrush(QColor("#9CACB4")) self.setBrush(self.normalBrush) self.shadow = QGraphicsDropShadowEffect( blurRadius=10, color=QColor(SHADOW_COLOR), offset=QPointF(0, 0) ) self.setGraphicsEffect(self.shadow) self.shadow.setEnabled(False) # Does this item have any anchored links. self.anchored = False if isinstance(parent, NodeItem): self.__parentNodeItem = parent else: self.__parentNodeItem = None self.__anchorPath = QPainterPath() self.__points = [] self.__pointPositions = [] self.__fullStroke = None self.__dottedStroke = None self.__shape = None def parentNodeItem(self): """ Return a parent :class:`NodeItem` or ``None`` if this anchor's parent is not a :class:`NodeItem` instance. """ return self.__parentNodeItem 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) def anchorPath(self): """ Return the anchor path (:class:`QPainterPath`). This is a curve on which the anchor points lie. """ return self.__anchorPath def setAnchored(self, anchored): """ Set the items anchored state. When ``False`` the item draws it self with a dotted stroke. """ self.anchored = anchored if anchored: self.setPath(self.__fullStroke) self.setBrush(self.connectedBrush) else: self.setPath(self.__dottedStroke) self.setBrush(self.normalBrush) def setConnectionHint(self, hint=None): """ Set the connection hint. This can be used to indicate if a connection can be made or not. """ raise NotImplementedError def count(self): """ Return the number of anchor points. """ return len(self.__points) def addAnchor(self, anchor, position=0.5): """ Add a new :class:`AnchorPoint` to this item and return it's index. The `position` specifies where along the `anchorPath` is the new point inserted. """ return self.insertAnchor(self.count(), anchor, position) def insertAnchor(self, index, anchor, position=0.5): """ Insert a new :class:`AnchorPoint` at `index`. See also -------- NodeAnchorItem.addAnchor """ if anchor in self.__points: raise ValueError("%s already added." % anchor) self.__points.insert(index, anchor) self.__pointPositions.insert(index, position) anchor.setParentItem(self) anchor.setPos(self.__anchorPath.pointAtPercent(position)) anchor.destroyed.connect(self.__onAnchorDestroyed) self.__updatePositions() self.setAnchored(bool(self.__points)) return index def removeAnchor(self, anchor): """ Remove and delete the anchor point. """ anchor = self.takeAnchor(anchor) anchor.hide() anchor.setParentItem(None) anchor.deleteLater() def takeAnchor(self, anchor): """ Remove the anchor but don't delete it. """ index = self.__points.index(anchor) del self.__points[index] del self.__pointPositions[index] anchor.destroyed.disconnect(self.__onAnchorDestroyed) self.__updatePositions() self.setAnchored(bool(self.__points)) return anchor def __onAnchorDestroyed(self, anchor): try: index = self.__points.index(anchor) except ValueError: return del self.__points[index] del self.__pointPositions[index] def anchorPoints(self): """ Return a list of anchor points. """ return list(self.__points) def anchorPoint(self, index): """ Return the anchor point at `index`. """ return self.__points[index] def setAnchorPositions(self, positions): """ Set the anchor positions in percentages (0..1) along the path curve. """ if self.__pointPositions != positions: self.__pointPositions = list(positions) self.__updatePositions() def anchorPositions(self): """ Return the positions of anchor points as a list of floats where each float is between 0 and 1 and specifies where along the anchor path does the point lie (0 is at start 1 is at the end). """ return list(self.__pointPositions) def shape(self): if self.__shape is not None: return self.__shape else: return GraphicsPathObject.shape(self) def hoverEnterEvent(self, event): self.shadow.setEnabled(True) return GraphicsPathObject.hoverEnterEvent(self, event) def hoverLeaveEvent(self, event): self.shadow.setEnabled(False) return GraphicsPathObject.hoverLeaveEvent(self, event) def __updatePositions(self): """Update anchor points positions. """ for point, t in zip(self.__points, self.__pointPositions): pos = self.__anchorPath.pointAtPercent(t) point.setPos(pos)
class ArrowAnnotation(Annotation): def __init__(self, parent=None, line=None, **kwargs): Annotation.__init__(self, parent, **kwargs) self.setFlag(QGraphicsItem.ItemIsMovable) self.setFlag(QGraphicsItem.ItemIsSelectable) self.setFocusPolicy(Qt.ClickFocus) if line is None: line = QLineF(0, 0, 20, 0) self.__line = QLineF(line) self.__color = QColor(Qt.red) # An item with the same shape as this arrow, stacked behind this # item as a source for QGraphicsDropShadowEffect. Cannot attach # the effect to this item directly as QGraphicsEffect makes the item # non devicePixelRatio aware. self.__arrowShadowBase = ArrowItem(self, line=line) self.__arrowShadowBase.setPen(Qt.NoPen) # no pen -> slightly thinner self.__arrowShadowBase.setBrush(QBrush(self.__color)) self.__arrowShadowBase.setArrowStyle(ArrowItem.Concave) self.__arrowShadowBase.setLineWidth(5) self.__shadow = QGraphicsDropShadowEffect( blurRadius=5, offset=QPointF(1.0, 2.0), ) self.__arrowShadowBase.setGraphicsEffect(self.__shadow) self.__shadow.setEnabled(True) # The 'real' shape item self.__arrowItem = ArrowItem(self, line=line) self.__arrowItem.setBrush(self.__color) self.__arrowItem.setPen(QPen(self.__color)) self.__arrowItem.setArrowStyle(ArrowItem.Concave) self.__arrowItem.setLineWidth(5) self.__autoAdjustGeometry = True def setAutoAdjustGeometry(self, autoAdjust): """ If set to `True` then the geometry will be adjusted whenever the arrow is changed with `setLine`. Otherwise the geometry of the item is only updated so the `line` lies within the `geometry()` rect (i.e. it only grows). True by default """ self.__autoAdjustGeometry = autoAdjust if autoAdjust: self.adjustGeometry() def autoAdjustGeometry(self): """ Should the geometry of the item be adjusted automatically when `setLine` is called. """ return self.__autoAdjustGeometry 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 line(self): """ Return the arrow base line (`QLineF` in object coordinates). """ return QLineF(self.__line) def setColor(self, color): """ Set arrow brush color. """ if self.__color != color: self.__color = QColor(color) self.__updateStyleState() def color(self): """ Return the arrow brush color. """ return QColor(self.__color) def setLineWidth(self, lineWidth): """ Set the arrow line width. """ self.__arrowItem.setLineWidth(lineWidth) self.__arrowShadowBase.setLineWidth(lineWidth) def lineWidth(self): """ Return the arrow line width. """ return self.__arrowItem.lineWidth() 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) def shape(self): arrow_shape = self.__arrowItem.shape() return self.mapFromItem(self.__arrowItem, arrow_shape) def itemChange(self, change, value): if change == QGraphicsItem.ItemSelectedHasChanged: self.__updateStyleState() return Annotation.itemChange(self, change, value) def __updateStyleState(self): """ Update the arrows' brush, pen, ... based on it's state """ if self.isSelected(): color = self.__color.darker(150) pen = QPen(QColor(96, 158, 215), Qt.DashDotLine) pen.setWidthF(1.25) pen.setCosmetic(True) shadow = pen.color().darker(150) else: color = self.__color pen = QPen(color) shadow = QColor(63, 63, 63, 180) self.__arrowShadowBase.setBrush(color) self.__shadow.setColor(shadow) self.__arrowItem.setBrush(color) self.__arrowItem.setPen(pen)