예제 #1
0
    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
예제 #2
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 = 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
예제 #3
0
    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)
예제 #4
0
    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)
예제 #5
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 = 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
예제 #6
0
    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
예제 #7
0
    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)
예제 #9
0
    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))
예제 #10
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)
예제 #11
0
 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)
예제 #12
0
    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
예제 #13
0
    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)])
예제 #14
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)
예제 #15
0
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)
예제 #16
0
    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)])
예제 #17
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)
예제 #18
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)
예제 #19
0
    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))
예제 #20
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
예제 #21
0
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)
예제 #22
0
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)
예제 #23
0
    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)])
예제 #24
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
예제 #25
0
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)
예제 #26
0
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)
예제 #27
0
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)
예제 #28
0
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)
예제 #29
0
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)
예제 #30
0
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)
예제 #31
0
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)
예제 #32
0
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)
예제 #33
0
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)
예제 #34
0
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)