示例#1
0
 def set_functions_list(self, functionsList, prefix="", sufix=""):
     self.funtionsList = functionsList
     self.funtionsListItems = []
     self.maxHeight = 0
     tempHeight = 0
     for element in functionsList:
         tempElement = QGraphicsTextItem(self)
         tempElement.setPlainText(prefix + element + sufix)
         tempElement.setPos(0, tempHeight)
         tempHeight += tempElement.document().size().height()
         if self.maxWidth < tempElement.document().size().width():
             self.maxWidth = tempElement.document().size().width()
         self.funtionsListItems.append(tempElement)
     self.maxHeight = tempHeight
示例#2
0
 def set_functions_list(self, functionsList, prefix="", sufix=""):
     self.funtionsList = functionsList
     self.funtionsListItems = []
     self.maxHeight = 0
     tempHeight = 0
     for element in functionsList:
         tempElement = QGraphicsTextItem(self)
         tempElement.setPlainText(prefix + element + sufix)
         tempElement.setPos(0, tempHeight)
         tempHeight += tempElement.document().size().height()
         if self.maxWidth < tempElement.document().size().width():
             self.maxWidth = tempElement.document().size().width()
         self.funtionsListItems.append(tempElement)
     self.maxHeight = tempHeight
class NodeItem(QGraphicsObject):
    """
    An widget node item in the canvas.
    """

    #: Signal emitted when the scene position of the node has changed.
    positionChanged = Signal()

    #: Signal emitted when the geometry of the channel anchors changes.
    anchorGeometryChanged = Signal()

    #: Signal emitted when the item has been activated (by a mouse double
    #: click or a keyboard)
    activated = Signal()

    #: The item is under the mouse.
    hovered = Signal()

    #: Span of the anchor in degrees
    ANCHOR_SPAN_ANGLE = 90

    #: Z value of the item
    Z_VALUE = 100

    def __init__(self, widget_description=None, parent=None, **kwargs):
        QGraphicsObject.__init__(self, parent, **kwargs)
        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
        self.setFlag(QGraphicsItem.ItemHasNoContents, True)
        self.setFlag(QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.setFlag(QGraphicsItem.ItemIsFocusable, True)

        # central body shape item
        self.shapeItem = None

        # in/output anchor items
        self.inputAnchorItem = None
        self.outputAnchorItem = None

        # title text item
        self.captionTextItem = None

        # error, warning, info items
        self.errorItem = None
        self.warningItem = None
        self.infoItem = None

        self.__title = ""
        self.__processingState = 0
        self.__progress = -1

        self.__error = None
        self.__warning = None
        self.__info = None

        self.__anchorLayout = None
        self.__animationEnabled = False

        self.setZValue(self.Z_VALUE)
        self.setupGraphics()

        self.setWidgetDescription(widget_description)

    @classmethod
    def from_node(cls, node):
        """
        Create an :class:`NodeItem` instance and initialize it from a
        :class:`SchemeNode` instance.

        """
        self = cls()
        self.setWidgetDescription(node.description)
#        self.setCategoryDescription(node.category)
        return self

    @classmethod
    def from_node_meta(cls, meta_description):
        """
        Create an `NodeItem` instance from a node meta description.
        """
        self = cls()
        self.setWidgetDescription(meta_description)
        return self

    def setupGraphics(self):
        """
        Set up the graphics.
        """
        shape_rect = QRectF(-24, -24, 48, 48)

        self.shapeItem = NodeBodyItem(self)
        self.shapeItem.setShapeRect(shape_rect)
        self.shapeItem.setAnimationEnabled(self.__animationEnabled)

        # Rect for widget's 'ears'.
        anchor_rect = QRectF(-31, -31, 62, 62)
        self.inputAnchorItem = SinkAnchorItem(self)
        input_path = QPainterPath()
        start_angle = 180 - self.ANCHOR_SPAN_ANGLE / 2
        input_path.arcMoveTo(anchor_rect, start_angle)
        input_path.arcTo(anchor_rect, start_angle, self.ANCHOR_SPAN_ANGLE)
        self.inputAnchorItem.setAnchorPath(input_path)

        self.outputAnchorItem = SourceAnchorItem(self)
        output_path = QPainterPath()
        start_angle = self.ANCHOR_SPAN_ANGLE / 2
        output_path.arcMoveTo(anchor_rect, start_angle)
        output_path.arcTo(anchor_rect, start_angle, - self.ANCHOR_SPAN_ANGLE)
        self.outputAnchorItem.setAnchorPath(output_path)

        self.inputAnchorItem.hide()
        self.outputAnchorItem.hide()

        # Title caption item
        self.captionTextItem = QGraphicsTextItem(self)
        self.captionTextItem.setPlainText("")
        self.captionTextItem.setPos(0, 33)

        def iconItem(standard_pixmap):
            item = GraphicsIconItem(self, icon=standard_icon(standard_pixmap),
                                    iconSize=QSize(16, 16))
            item.hide()
            return item

        self.errorItem = iconItem(QStyle.SP_MessageBoxCritical)
        self.warningItem = iconItem(QStyle.SP_MessageBoxWarning)
        self.infoItem = iconItem(QStyle.SP_MessageBoxInformation)

    # TODO: Remove the set[Widget|Category]Description. The user should
    # handle setting of icons, title, ...
    def setWidgetDescription(self, desc):
        """
        Set widget description.
        """
        self.widget_description = desc
        if desc is None:
            return

        icon = icon_loader.from_description(desc).get(desc.icon)
        if icon:
            self.setIcon(icon)

        if not self.title():
            self.setTitle(desc.name)

        if desc.inputs:
            self.inputAnchorItem.show()
        if desc.outputs:
            self.outputAnchorItem.show()

        tooltip = NodeItem_toolTipHelper(self)
        self.setToolTip(tooltip)

    def setWidgetCategory(self, desc):
        """
        Set the widget category.
        """
        self.category_description = desc
        if desc and desc.background:
            background = NAMED_COLORS.get(desc.background, desc.background)
            color = QColor(background)
            if color.isValid():
                self.setColor(color)

    def setIcon(self, icon):
        """
        Set the node item's icon (:class:`QIcon`).
        """
        if isinstance(icon, QIcon):
            self.icon_item = GraphicsIconItem(self.shapeItem, icon=icon,
                                              iconSize=QSize(36, 36))
            self.icon_item.setPos(-18, -18)
        else:
            raise TypeError

    def setColor(self, color, selectedColor=None):
        """
        Set the widget color.
        """
        if selectedColor is None:
            selectedColor = saturated(color, 150)
        palette = create_palette(color, selectedColor)
        self.shapeItem.setPalette(palette)

    def setPalette(self, palette):
        # TODO: The palette should override the `setColor`
        raise NotImplementedError

    def setTitle(self, title):
        """
        Set the node title. The title text is displayed at the bottom of the
        node.

        """
        self.__title = title
        self.__updateTitleText()

    def title(self):
        """
        Return the node title.
        """
        return self.__title

    title_ = Property(unicode, fget=title, fset=setTitle,
                      doc="Node title text.")

    def setFont(self, font):
        """
        Set the title text font (:class:`QFont`).
        """
        if font != self.font():
            self.prepareGeometryChange()
            self.captionTextItem.setFont(font)
            self.__updateTitleText()

    def font(self):
        """
        Return the title text font.
        """
        return self.captionTextItem.font()

    def setAnimationEnabled(self, enabled):
        """
        Set the node animation enabled state.
        """
        if self.__animationEnabled != enabled:
            self.__animationEnabled = enabled
            self.shapeItem.setAnimationEnabled(enabled)

    def animationEnabled(self):
        """
        Are node animations enabled.
        """
        return self.__animationEnabled

    def setProcessingState(self, state):
        """
        Set the node processing state i.e. the node is processing
        (is busy) or is idle.

        """
        if self.__processingState != state:
            self.__processingState = state
            self.shapeItem.setProcessingState(state)
            if not state:
                # Clear the progress meter.
                self.setProgress(-1)
                if self.__animationEnabled:
                    self.shapeItem.ping()

    def processingState(self):
        """
        The node processing state.
        """
        return self.__processingState

    processingState_ = Property(int, fget=processingState,
                                fset=setProcessingState)

    def setProgress(self, progress):
        """
        Set the node work progress state (number between 0 and 100).
        """
        if progress is None or progress < 0:
            progress = -1

        progress = max(min(progress, 100), -1)
        if self.__progress != progress:
            self.__progress = progress
            self.shapeItem.setProgress(progress)
            self.__updateTitleText()

    def progress(self):
        """
        Return the node work progress state.
        """
        return self.__progress

    progress_ = Property(float, fget=progress, fset=setProgress,
                         doc="Node progress state.")

    def setProgressMessage(self, message):
        """
        Set the node work progress message.

        .. note:: Not yet implemented

        """
        pass

    def setErrorMessage(self, message):
        if self.__error != message:
            self.__error = message
            self.__updateMessages()

    def setWarningMessage(self, message):
        if self.__warning != message:
            self.__warning = message
            self.__updateMessages()

    def setInfoMessage(self, message):
        if self.__info != message:
            self.__info = message
            self.__updateMessages()

    def newInputAnchor(self):
        """
        Create and return a new input :class:`AnchorPoint`.
        """
        if not (self.widget_description and self.widget_description.inputs):
            raise ValueError("Widget has no inputs.")

        anchor = AnchorPoint()
        self.inputAnchorItem.addAnchor(anchor, position=1.0)

        positions = self.inputAnchorItem.anchorPositions()
        positions = uniform_linear_layout(positions)
        self.inputAnchorItem.setAnchorPositions(positions)

        return anchor

    def removeInputAnchor(self, anchor):
        """
        Remove input anchor.
        """
        self.inputAnchorItem.removeAnchor(anchor)

        positions = self.inputAnchorItem.anchorPositions()
        positions = uniform_linear_layout(positions)
        self.inputAnchorItem.setAnchorPositions(positions)

    def newOutputAnchor(self):
        """
        Create and return a new output :class:`AnchorPoint`.
        """
        if not (self.widget_description and self.widget_description.outputs):
            raise ValueError("Widget has no outputs.")

        anchor = AnchorPoint(self)
        self.outputAnchorItem.addAnchor(anchor, position=1.0)

        positions = self.outputAnchorItem.anchorPositions()
        positions = uniform_linear_layout(positions)
        self.outputAnchorItem.setAnchorPositions(positions)

        return anchor

    def removeOutputAnchor(self, anchor):
        """
        Remove output anchor.
        """
        self.outputAnchorItem.removeAnchor(anchor)

        positions = self.outputAnchorItem.anchorPositions()
        positions = uniform_linear_layout(positions)
        self.outputAnchorItem.setAnchorPositions(positions)

    def inputAnchors(self):
        """
        Return a list of all input anchor points.
        """
        return self.inputAnchorItem.anchorPoints()

    def outputAnchors(self):
        """
        Return a list of all output anchor points.
        """
        return self.outputAnchorItem.anchorPoints()

    def setAnchorRotation(self, angle):
        """
        Set the anchor rotation.
        """
        self.inputAnchorItem.setRotation(angle)
        self.outputAnchorItem.setRotation(angle)
        self.anchorGeometryChanged.emit()

    def anchorRotation(self):
        """
        Return the anchor rotation.
        """
        return self.inputAnchorItem.rotation()

    def boundingRect(self):
        # TODO: Important because of this any time the child
        # items change geometry the self.prepareGeometryChange()
        # needs to be called.
        return self.childrenBoundingRect()

    def shape(self):
        # Shape for mouse hit detection.
        # TODO: Should this return the union of all child items?
        return self.shapeItem.shape()

    def __updateTitleText(self):
        """
        Update the title text item.
        """
        title_safe = escape(self.title())
        if self.progress() > 0:
            text = '<div align="center">%s<br/>%i%%</div>' % \
                   (title_safe, int(self.progress()))
        else:
            text = '<div align="center">%s</div>' % \
                   (title_safe)

        # The NodeItems boundingRect could change.
        self.prepareGeometryChange()
        self.captionTextItem.setHtml(text)
        self.captionTextItem.document().adjustSize()
        width = self.captionTextItem.textWidth()
        self.captionTextItem.setPos(-width / 2.0, 33)

    def __updateMessages(self):
        """
        Update message items (position, visibility and tool tips).
        """
        items = [self.errorItem, self.warningItem, self.infoItem]
        messages = [self.__error, self.__warning, self.__info]
        for message, item in zip(messages, items):
            item.setVisible(bool(message))
            item.setToolTip(message or "")
        shown = [item for item in items if item.isVisible()]
        count = len(shown)
        if count:
            spacing = 3
            rects = [item.boundingRect() for item in shown]
            width = sum(rect.width() for rect in rects)
            width += spacing * max(0, count - 1)
            height = max(rect.height() for rect in rects)
            origin = self.shapeItem.boundingRect().top() - spacing - height
            origin = QPointF(-width / 2, origin)
            for item, rect in zip(shown, rects):
                item.setPos(origin)
                origin = origin + QPointF(rect.width() + spacing, 0)

    def mousePressEvent(self, event):
        if self.shapeItem.path().contains(event.pos()):
            return QGraphicsObject.mousePressEvent(self, event)
        else:
            event.ignore()

    def mouseDoubleClickEvent(self, event):
        if self.shapeItem.path().contains(event.pos()):
            QGraphicsObject.mouseDoubleClickEvent(self, event)
            QTimer.singleShot(0, self.activated.emit)
        else:
            event.ignore()

    def contextMenuEvent(self, event):
        if self.shapeItem.path().contains(event.pos()):
            return QGraphicsObject.contextMenuEvent(self, event)
        else:
            event.ignore()

    def focusInEvent(self, event):
        self.shapeItem.setHasFocus(True)
        return QGraphicsObject.focusInEvent(self, event)

    def focusOutEvent(self, event):
        self.shapeItem.setHasFocus(False)
        return QGraphicsObject.focusOutEvent(self, event)

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemSelectedChange:
            self.shapeItem.setSelected(value.toBool())
        elif change == QGraphicsItem.ItemPositionHasChanged:
            self.positionChanged.emit()

        return QGraphicsObject.itemChange(self, change, value)
示例#4
0
class EFrameLayout(QGraphicsPolygonItem):
    def __init__(self, parent=None, scene=None):
        super(EFrameLayout, self).__init__(parent, scene)

        self.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.setAcceptsHoverEvents(True)

        self.__name = QGraphicsTextItem()
        self.__name.setPlainText('Frame Layout')

        self.__handleWidth = 500
        self.__handleHeight = 20
        self.__separator = 5
        self.__boundExtra = 3 + self.pen().width()
        self.__handleRect = QRectF(0.0, 0.0, self.__handleWidth,
                                   self.__handleHeight)

        self.__controlsBound = 0.0
        self.__controls = []

        self.__isDefaultPen = False
        self.__isCollapsed = True

        self.__pens = {
            0: EDraw.EColor.DefaultLeaveHoverPen,
            1: EDraw.EColor.DefaultEnterHoverPen
        }
        self.setPen(self.__pens[self.__isDefaultPen])

    @property
    def Label(self):
        return self.__name.toPlainText()

    @Label.setter
    def Label(self, label):
        self.__name.setPlainText(str(label))

    @property
    def Width(self):
        return self.__handleWidth

    @Width.setter
    def Width(self, width):
        self.__handleWidth = width
        self.updateGeometry()

    def toggleContentVisibility(self):
        if not len(self.__controls): return

        if self.__controls[0].isVisible():
            [control.hide() for control in self.__controls]
            self.__isCollapsed = True
            return

        [control.show() for control in self.__controls]
        self.__isCollapsed = False

    def updateGeometry(self):

        self.__handleRect = QRectF(0.0, 0.0, self.__handleWidth,
                                   self.__handleHeight)

        if not len(self.__controls) or self.__isCollapsed:
            self.__controlsBound = 0.0
            return

        self.__controlsBound = 0.0
        self.__controlsBound = self.__separator
        self.__controlsBound += sum([
            control.boundingRect().height() + self.__separator
            for control in self.__controls
        ])

        step = self.__handleHeight + self.__separator * 2

        for control in self.__controls:

            control.Name.setTextWidth(self.__controlNameWidth)
            control.Width = self.__handleRect.normalized().adjusted(
                5.0, 0.0, -5.0, 0.0).width()

            control.setPos(5.0, step)
            step += control.boundingRect().height() + self.__separator

    def addControl(self, control):
        if not isinstance(control, EControlsGroup): raise AttributeError

        self.__controls.append(control)
        control.setParentItem(self)
        control.setFlag(QGraphicsItem.ItemIsMovable, False)

        self.__controlNameWidth = max([
            control.Name.boundingRect().width() for control in self.__controls
        ]) + 5

        self.__isCollapsed = False
        self.updateGeometry()

    def clear(self):
        [control.setParentItem(None) for control in self.__controls]
        self.__controls = []

        self.updateGeometry()

    def mouseDoubleClickEvent(self, mouseEvent):
        QGraphicsPolygonItem.mouseDoubleClickEvent(self, mouseEvent)

        self.toggleContentVisibility()
        self.updateGeometry()

    def shape(self):
        return QGraphicsItem.shape(self)

    def boundingRect(self):
        return self.__handleRect.normalized().adjusted(0.0, 0.0, 0.0,
                                                       self.__controlsBound)

    def paint(self, painter, option, widget=None):

        if not self.__isCollapsed:
            painter.setPen(QPen(QColor(0, 0, 0, 50), 2, Qt.SolidLine))
            painter.setBrush(QColor(0, 0, 0, 50))
            painter.drawRect(self.boundingRect().adjusted(
                self.pen().width(), self.__handleHeight, -self.pen().width(),
                0.0))

        painter.setPen(self.pen())
        painter.setBrush(
            EDraw.EColor.LinearGradient(self.__handleRect, Qt.darkGray))
        #painter.drawPolygon( QPolygonF( self.__handleRect.normalized().adjusted(-self.__boundExtra, 0.0, self.__boundExtra, 0.0 ) ) )
        painter.drawPolygon(QPolygonF(self.__handleRect))

        painter.setPen(Qt.lightGray)
        r = QRectF(0.0, 0.0,
                   self.__name.boundingRect().width(),
                   self.__handleRect.height())
        painter.drawText(r, Qt.AlignCenter, self.__name.toPlainText())
示例#5
0
class EControlsGroup(QGraphicsPolygonItem):
    def __init__(self, parent=None, scene=None):
        super(EControlsGroup, self).__init__(parent, scene)

        self.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.setAcceptsHoverEvents(True)

        self.__kId = str(uuid.uuid1())
        self.__name = QGraphicsTextItem()
        self.__name.setPlainText('EddGroup')

        self.__sWidth = 500
        self.__sHeight = 20
        self.__sExtra = 8
        self.__controlsOffset = 5

        self.__isDefaultPen = False

        self.__pens = {
            0: EDraw.EColor.DefaultLeaveHoverPen,
            1: EDraw.EColor.DefaultEnterHoverPen
        }
        self.setPen(self.__pens[self.__isDefaultPen])

        self.__shapeRect = QRectF(0, 0, self.__sWidth, self.__sHeight)
        self.__controls = []

    def updateGeometry(self):

        adjustableControls = [
            control for control in self.__controls if control.Adjustable
        ]

        if len(adjustableControls):
            fixedControlsBounds = self.__name.boundingRect().width()
            fixedControlsBounds += sum(
                map(int, [
                    control.boundingRect().width()
                    for control in self.__controls if not control.Adjustable
                ]))

            adjustableControlsWidth = int(
                (self.__sWidth -
                 fixedControlsBounds)) / len(adjustableControls)

        step = self.__name.boundingRect().width()

        for control in self.__controls:
            if control.Adjustable:
                control.Width = adjustableControlsWidth - self.__controlsOffset

            control.setPos(step, 0.0)
            step += control.boundingRect().width() + self.__controlsOffset

        self.__shapeRect = QRectF(
            0, -(self.__sExtra / 2), self.__sWidth,
            max([control.Height
                 for control in self.__controls]) + self.__sExtra)

    @property
    def kId(self):
        return self.__kId

    @property
    def Name(self):
        return self.__name

    @Name.setter
    def Name(self, name):
        " %s" % self.__name.setPlainText(name)

        self.updateGeometry()

    @property
    def Width(self):
        return self.__sWidth

    @Width.setter
    def Width(self, width):
        self.__sWidth = width  #- self.__controlsOffset * len( self.__controls )
        self.updateGeometry()

    @property
    def Height(self):
        return self.__sHeight

    @property
    def Controls(self):
        return self.__controls

    @Controls.setter
    def Controls(self, control):
        control.setParentItem(self)
        self.__controls.append(control)
        self.updateGeometry()

    def toggleHighlight(self):
        if self.__isDefaultPen:
            self.__isDefaultPen = False
            self.setPen(self.__pens[self.__isDefaultPen])
            return

        self.__isDefaultPen = True
        self.setPen(self.__pens[self.__isDefaultPen])

    def hoverEnterEvent(self, mouseEvent):
        QGraphicsPolygonItem.hoverEnterEvent(self, mouseEvent)
        self.toggleHighlight()

    def hoverLeaveEvent(self, mouseEvent):
        QGraphicsPolygonItem.hoverLeaveEvent(self, mouseEvent)
        self.toggleHighlight()

    def shape(self):
        return QGraphicsItem.shape(self)

    def boundingRect(self):
        return self.__shapeRect

    def paint(self, painter, option, widget=None):

        painter.setPen(self.pen())
        painter.setBrush(
            EDraw.EColor.LinearGradient(self.boundingRect(), Qt.darkGray))
        painter.drawPolygon(QPolygonF(self.boundingRect()))

        painter.setPen(Qt.lightGray)

        r = QRectF(0.0, -(self.__sExtra / 2),
                   self.__name.boundingRect().width(),
                   self.boundingRect().height())
        painter.drawText(r, Qt.AlignRight | Qt.AlignCenter,
                         "%s: " % self.__name.toPlainText())
示例#6
0
class LinkItem(QGraphicsObject):
    """
    A Link item in the canvas that connects two :class:`.NodeItem`\s in the
    canvas.

    The link curve connects two `Anchor` items (see :func:`setSourceItem`
    and :func:`setSinkItem`). Once the anchors are set the curve
    automatically adjusts its end points whenever the anchors move.

    An optional source/sink text item can be displayed above the curve's
    central point (:func:`setSourceName`, :func:`setSinkName`)

    """

    #: Z value of the item
    Z_VALUE = 0

    def __init__(self, *args):
        QGraphicsObject.__init__(self, *args)
        self.setFlag(QGraphicsItem.ItemHasNoContents, True)
        self.setAcceptedMouseButtons(Qt.RightButton | Qt.LeftButton)
        self.setAcceptHoverEvents(True)

        self.setZValue(self.Z_VALUE)

        self.sourceItem = None
        self.sourceAnchor = None
        self.sinkItem = None
        self.sinkAnchor = None

        self.curveItem = LinkCurveItem(self)

        self.sourceIndicator = LinkAnchorIndicator(self)
        self.sinkIndicator = LinkAnchorIndicator(self)
        self.sourceIndicator.hide()
        self.sinkIndicator.hide()

        self.linkTextItem = QGraphicsTextItem(self)

        self.__sourceName = ""
        self.__sinkName = ""

        self.__dynamic = False
        self.__dynamicEnabled = False

        self.hover = False

    def setSourceItem(self, item, anchor=None):
        """
        Set the source `item` (:class:`.NodeItem`). Use `anchor`
        (:class:`.AnchorPoint`) as the curve start point (if ``None`` a new
        output anchor will be created using ``item.newOutputAnchor()``).

        Setting item to ``None`` and a valid anchor is a valid operation
        (for instance while mouse dragging one end of the link).

        """
        if item is not None and anchor is not None:
            if anchor not in item.outputAnchors():
                raise ValueError("Anchor must be belong to the item")

        if self.sourceItem != item:
            if self.sourceAnchor:
                # Remove a previous source item and the corresponding anchor
                self.sourceAnchor.scenePositionChanged.disconnect(
                    self._sourcePosChanged
                )

                if self.sourceItem is not None:
                    self.sourceItem.removeOutputAnchor(self.sourceAnchor)

                self.sourceItem = self.sourceAnchor = None

            self.sourceItem = item

            if item is not None and anchor is None:
                # Create a new output anchor for the item if none is provided.
                anchor = item.newOutputAnchor()

            # Update the visibility of the start point indicator.
            self.sourceIndicator.setVisible(bool(item))

        if anchor != self.sourceAnchor:
            if self.sourceAnchor is not None:
                self.sourceAnchor.scenePositionChanged.disconnect(
                    self._sourcePosChanged
                )

            self.sourceAnchor = anchor

            if self.sourceAnchor is not None:
                self.sourceAnchor.scenePositionChanged.connect(
                    self._sourcePosChanged
                )

        self.__updateCurve()

    def setSinkItem(self, item, anchor=None):
        """
        Set the sink `item` (:class:`.NodeItem`). Use `anchor`
        (:class:`.AnchorPoint`) as the curve end point (if ``None`` a new
        input anchor will be created using ``item.newInputAnchor()``).

        Setting item to ``None`` and a valid anchor is a valid operation
        (for instance while mouse dragging one and of the link).

        """
        if item is not None and anchor is not None:
            if anchor not in item.inputAnchors():
                raise ValueError("Anchor must be belong to the item")

        if self.sinkItem != item:
            if self.sinkAnchor:
                # Remove a previous source item and the corresponding anchor
                self.sinkAnchor.scenePositionChanged.disconnect(
                    self._sinkPosChanged
                )

                if self.sinkItem is not None:
                    self.sinkItem.removeInputAnchor(self.sinkAnchor)

                self.sinkItem = self.sinkAnchor = None

            self.sinkItem = item

            if item is not None and anchor is None:
                # Create a new input anchor for the item if none is provided.
                anchor = item.newInputAnchor()

            # Update the visibility of the end point indicator.
            self.sinkIndicator.setVisible(bool(item))

        if self.sinkAnchor != anchor:
            if self.sinkAnchor is not None:
                self.sinkAnchor.scenePositionChanged.disconnect(
                    self._sinkPosChanged
                )

            self.sinkAnchor = anchor

            if self.sinkAnchor is not None:
                self.sinkAnchor.scenePositionChanged.connect(
                    self._sinkPosChanged
                )

        self.__updateCurve()

    def setFont(self, font):
        """
        Set the font for the channel names text item.
        """
        if font != self.font():
            self.linkTextItem.setFont(font)
            self.__updateText()

    def font(self):
        """
        Return the font for the channel names text.
        """
        return self.linkTextItem.font()

    def setChannelNamesVisible(self, visible):
        """
        Set the visibility of the channel name text.
        """
        self.linkTextItem.setVisible(visible)

    def setSourceName(self, name):
        """
        Set the name of the source (used in channel name text).
        """
        if self.__sourceName != name:
            self.__sourceName = name
            self.__updateText()

    def sourceName(self):
        """
        Return the source name.
        """
        return self.__sourceName

    def setSinkName(self, name):
        """
        Set the name of the sink (used in channel name text).
        """
        if self.__sinkName != name:
            self.__sinkName = name
            self.__updateText()

    def sinkName(self):
        """
        Return the sink name.
        """
        return self.__sinkName

    def _sinkPosChanged(self, *arg):
        self.__updateCurve()

    def _sourcePosChanged(self, *arg):
        self.__updateCurve()

    def __updateCurve(self):
        self.prepareGeometryChange()
        if self.sourceAnchor and self.sinkAnchor:
            source_pos = self.sourceAnchor.anchorScenePos()
            sink_pos = self.sinkAnchor.anchorScenePos()
            source_pos = self.curveItem.mapFromScene(source_pos)
            sink_pos = self.curveItem.mapFromScene(sink_pos)

            # Adaptive offset for the curve control points to avoid a
            # cusp when the two points have the same y coordinate
            # and are close together
            delta = source_pos - sink_pos
            dist = math.sqrt(delta.x() ** 2 + delta.y() ** 2)
            cp_offset = min(dist / 2.0, 60.0)

            # TODO: make the curve tangent orthogonal to the anchors path.
            path = QPainterPath()
            path.moveTo(source_pos)
            path.cubicTo(source_pos + QPointF(cp_offset, 0),
                         sink_pos - QPointF(cp_offset, 0),
                         sink_pos)

            self.curveItem.setPath(path)
            self.sourceIndicator.setPos(source_pos)
            self.sinkIndicator.setPos(sink_pos)
            self.__updateText()
        else:
            self.setHoverState(False)
            self.curveItem.setPath(QPainterPath())

    def __updateText(self):
        self.prepareGeometryChange()

        if self.__sourceName or self.__sinkName:
            if self.__sourceName != self.__sinkName:
                text = u"{0} \u2192 {1}".format(self.__sourceName,
                                                self.__sinkName)
            else:
                # If the names are the same show only one.
                # Is this right? If the sink has two input channels of the
                # same type having the name on the link help elucidate
                # the scheme.
                text = self.__sourceName
        else:
            text = ""

        self.linkTextItem.setPlainText(text)

        path = self.curveItem.path()
        if not path.isEmpty():
            center = path.pointAtPercent(0.5)
            angle = path.angleAtPercent(0.5)

            brect = self.linkTextItem.boundingRect()

            transform = QTransform()
            transform.translate(center.x(), center.y())
            transform.rotate(-angle)

            # Center and move above the curve path.
            transform.translate(-brect.width() / 2, -brect.height())

            self.linkTextItem.setTransform(transform)

    def removeLink(self):
        self.setSinkItem(None)
        self.setSourceItem(None)
        self.__updateCurve()

    def setHoverState(self, state):
        if self.hover != state:
            self.prepareGeometryChange()
            self.hover = state
            self.sinkIndicator.setHoverState(state)
            self.sourceIndicator.setHoverState(state)
            self.curveItem.setHoverState(state)

    def hoverEnterEvent(self, event):
        # Hover enter event happens when the mouse enters any child object
        # but we only want to show the 'hovered' shadow when the mouse
        # is over the 'curveItem', so we install self as an event filter
        # on the LinkCurveItem and listen to its hover events.
        self.curveItem.installSceneEventFilter(self)
        return QGraphicsObject.hoverEnterEvent(self, event)

    def hoverLeaveEvent(self, event):
        # Remove the event filter to prevent unnecessary work in
        # scene event filter when not needed
        self.curveItem.removeSceneEventFilter(self)
        return QGraphicsObject.hoverLeaveEvent(self, event)

    def sceneEventFilter(self, obj, event):
        if obj is self.curveItem:
            if event.type() == QEvent.GraphicsSceneHoverEnter:
                self.setHoverState(True)
            elif event.type() == QEvent.GraphicsSceneHoverLeave:
                self.setHoverState(False)

        return QGraphicsObject.sceneEventFilter(self, obj, event)

    def boundingRect(self):
        return self.childrenBoundingRect()

    def shape(self):
        return self.curveItem.shape()

    def setEnabled(self, enabled):
        """
        Reimplemented from :class:`QGraphicsObject`

        Set link enabled state. When disabled the link is rendered with a
        dashed line.

        """
        # This getter/setter pair override a property from the base class.
        # They should be renamed to e.g. setLinkEnabled/linkEnabled
        self.curveItem.setLinkEnabled(enabled)

    def isEnabled(self):
        return self.curveItem.isLinkEnabled()

    def setDynamicEnabled(self, enabled):
        """
        Set the link's dynamic enabled state.

        If the link is `dynamic` it will be rendered in red/green color
        respectively depending on the state of the dynamic enabled state.

        """
        if self.__dynamicEnabled != enabled:
            self.__dynamicEnabled = enabled
            if self.__dynamic:
                self.__updatePen()

    def isDynamicEnabled(self):
        """
        Is the link dynamic enabled.
        """
        return self.__dynamicEnabled

    def setDynamic(self, dynamic):
        """
        Mark the link as dynamic (i.e. it responds to
        :func:`setDynamicEnabled`).

        """
        if self.__dynamic != dynamic:
            self.__dynamic = dynamic
            self.__updatePen()

    def isDynamic(self):
        """
        Is the link dynamic.
        """
        return self.__dynamic

    def __updatePen(self):
        self.prepareGeometryChange()
        if self.__dynamic:
            if self.__dynamicEnabled:
                color = QColor(0, 150, 0, 150)
            else:
                color = QColor(150, 0, 0, 150)

            normal = QPen(QBrush(color), 2.0)
            hover = QPen(QBrush(color.darker(120)), 2.1)
        else:
            normal = QPen(QBrush(QColor("#9CACB4")), 2.0)
            hover = QPen(QBrush(QColor("#7D7D7D")), 2.1)

        self.curveItem.setCurvePenSet(normal, hover)
class TaskGraphicsItem (QGraphicsEllipseItem):

    def __init__(self, rect=None):
        super(TaskGraphicsItem, self).__init__()

        if rect is not None:
            self.setRect(rect)

        self.setPen(QPen(Qt.NoPen))

        # Setup the text item
        self.textItem = QGraphicsTextItem()
        self.textItem.setParentItem(self)
        self.textItem.rotate(-90)
        self.textItem.setDefaultTextColor(QColor(255, 255, 255))

        # The dimensions to reach via a LERP.
        self.startPos = QPointF(0, 0)
        self.endPos = QPointF(0, 0)
        self.startDiameter = 1
        self.endDiameter = 1

        self.centerMark = QGraphicsEllipseItem()
        self.centerMark.setBrush(QBrush(Qt.white))
        self.centerMark.setPen(QPen(Qt.NoPen))
        self.centerMark.setParentItem(self)

        self.pid = -1

        # To determine if it is associated with an active process.
        self.used = False

    def mousePressEvent(self, event):
        print "Clicked On Ellipse at: ", self.rect().topLeft()

    def set_pid(self, pid):
        self.pid = pid

    def set_name(self, str_name):
        self.textItem.setPlainText(str_name)

    def update_name_pos(self):

        rect = self.boundingRect()
        text_rect = self.textItem.boundingRect()

        # Center text (on the x-axis) and offset (on the y-axis) so it doesn't overlap the ellipse item.
        x_text = rect.x() + rect.width()/2 - text_rect.height()/2
        y_text = rect.y() + 100 + text_rect.width() + rect.height()

        self.textItem.setPos(x_text, y_text)

    # Time step is in seconds.
    def update(self, time_step):

        diameter = self.rect().width() + self.lerp_rate(self.startDiameter, self.endDiameter, time_step)
        if diameter <= 1:
            diameter = 1

        pos = self.rect().topLeft()

        x = pos.x() + self.lerp_rate(self.startPos.x(), self.endPos.x(), time_step)
        y = pos.y() + self.lerp_rate(self.startPos.y(), self.endPos.y(), time_step)

        self.setRect(QRectF(x, y, diameter, diameter))
        self.update_name_pos()
        self.update_center_mark()

    def update_center_mark(self):
        scale = self.scene().views()[0].currentScale

        hwidth = self.rect().width() / 2.0
        diam = 2.0 / scale

        # Only mark center for large enough items.
        if hwidth * 0.2 > diam:

            self.centerMark.setVisible(True)

            hdiam = diam / 2.0
            pos = self.rect().topLeft()

            x = pos.x() - hdiam + hwidth
            y = pos.y() - hdiam + hwidth
            self.centerMark.setRect(QRectF(x, y, diam, diam))

        else:
            self.centerMark.setVisible(False)

    # Return the linear interpolation rate. Reach start to end at a rate of 'growth rate'
    @staticmethod
    def lerp_rate(start, end, time_step):
        return (end - start) * time_step
示例#8
0
class VennIntersectionArea(QGraphicsPathItem):
    def __init__(self, parent=None, text=""):
        super(QGraphicsPathItem, self).__init__(parent)
        self.setAcceptHoverEvents(True)
        self.setPen(QPen(Qt.NoPen))

        self.text = QGraphicsTextItem(self)
        layout = self.text.document().documentLayout()
        layout.documentSizeChanged.connect(self._onLayoutChanged)

        self._text = ""
        self._anchor = QPointF()

    def setText(self, text):
        if self._text != text:
            self._text = text
            self.text.setPlainText(text)

    def text(self):
        return self._text

    def setTextAnchor(self, pos):
        if self._anchor != pos:
            self._anchor = pos
            self._updateTextAnchor()

    def hoverEnterEvent(self, event):
        self.setZValue(self.zValue() + 1)
        return QGraphicsPathItem.hoverEnterEvent(self, event)

    def hoverLeaveEvent(self, event):
        self.setZValue(self.zValue() - 1)
        return QGraphicsPathItem.hoverLeaveEvent(self, event)

    def paint(self, painter, option, widget=None):
        painter.save()
        path = self.path()
        brush = QBrush(self.brush())
        pen = QPen(self.pen())

        if option.state & QStyle.State_Selected:
            pen.setColor(Qt.red)
            brush.setStyle(Qt.DiagCrossPattern)
            brush.setColor(QColor(40, 40, 40, 100))

        elif option.state & QStyle.State_MouseOver:
            pen.setColor(Qt.blue)

        if option.state & QStyle.State_MouseOver:
            brush.setColor(QColor(100, 100, 100, 100))
            if brush.style() == Qt.NoBrush:
                # Make sure the highlight is actually visible.
                brush.setStyle(Qt.SolidPattern)

        painter.setPen(pen)
        painter.setBrush(brush)
        painter.drawPath(path)
        painter.restore()

    def itemChange(self, change, value):
        if change == QGraphicsPathItem.ItemSelectedHasChanged:
            self.setZValue(self.zValue() + (1 if value else -1))
        return QGraphicsPathItem.itemChange(self, change, value)

    def _updateTextAnchor(self):
        rect = self.text.boundingRect()
        pos = anchor_rect(rect, self._anchor)
        self.text.setPos(pos)

    def _onLayoutChanged(self):
        self._updateTextAnchor()
示例#9
0
class ClassModel(QGraphicsItem):

    def __init__(self, parent=None, graphicView=None, graphicScene=None):
        QGraphicsItem.__init__(self)
        self.set_default_data()
        self.className = QGraphicsTextItem(self)
        self.functionsItem = FunctionsContainerModel(self)
        self.className.setPlainText(self.defaultClassName)
        self.setFlag(self.ItemIsMovable)
        self.setFlag(self.ItemSendsGeometryChanges)
        self.functionsItem.setPos(0, self.__get_title_height())
        self.attributesItem = FunctionsContainerModel(self)
        self.attributesItem.setPos(0, self.functionsItem.get_height())

    def set_default_data(self):
        self.maxWidth = 100
        self.defaultClassNameHeight = 30
        self.defaultClassName = "No name"

    def set_functions_list(self, functionsList):
        self.functionsItem.set_functions_list(functionsList, "*", "()")
        self.update_positions()

    def set_attributes_list(self, attributesList):
        self.attributesItem.set_functions_list(attributesList)
        self.update_positions()

    def set_class_name(self, className):
        self.className.setPlainText(className)

    def _get_width(self):
        self.__calc_max_width()
        return self.maxWidth

    def __get_title_height(self):
        titleHeight = self.defaultClassNameHeight
        if titleHeight == self.className.document().size().height():
            titleHeight = self.className.document().size().height()
        return titleHeight

    def get_height(self):
        summary = self.defaultClassNameHeight
        summary += self.functionsItem.get_height()
        summary += self.attributesItem.get_height()
        return summary

    def __calc_max_width(self):
        if self.maxWidth < self.className.document().size().width():
            self.maxWidth = self.className.document().size().width()
        if hasattr(self, "functionsItem"):
            if self.maxWidth < self.functionsItem.get_width():
                self.maxWidth = self.functionsItem.get_width()
        if hasattr(self, "attributesItem"):
            if self.maxWidth < self.attributesItem.get_width():
                self.maxWidth = self.attributesItem.get_width()

    def set_bg_color(self, qColor):
        self.backgroundColor = qColor

    def set_method_list(self, itemList):
        self.methodList = itemList

    def update_positions(self):
        self.functionsItem.setPos(0, self.__get_title_height())
        self.attributesItem.setPos(
            0, self.functionsItem.y() + self.functionsItem.get_height())

    def paint(self, painter, option, widget):
        gradient = QRadialGradient(-3, -3, 10)
        if option.state & QStyle.State_Sunken:
            gradient.setCenter(3, 3)
            gradient.setFocalPoint(3, 3)
            gradient.setColorAt(0, QColor(Qt.yellow).light(120))
        else:
            gradient.setColorAt(0, QColor(Qt.yellow).light(120))
        painter.setBrush(gradient)
        painter.setPen(QPen(Qt.black, 0))
        painter.drawRoundedRect(self.boundingRect(), 3, 3)

    def boundingRect(self):
        return QRectF(0, 0, self._get_width(), self.get_height())

    def add_edge(self, edge):
        self.myEdge = edge
        edge.adjust()
示例#10
0
文件: pageView.py 项目: kzwkt/dff
class pageView(QGraphicsView):
    def __init__(self, wpage):
        QGraphicsView.__init__(self)
        self.init(wpage)
        self.initShape()

#        self.selection = pageSelection(self.heditor)

    def init(self, wpage):
        self.wpage = wpage
        self.heditor = wpage.heditor
        self.filesize = self.heditor.filesize
        self.start = True

        self.pagew = 20
        self.pageh = 20

        self.pageItems = []
        self.offsetItems = []
        self.displayLines = 0

    def initShape(self):
        #Scene
        self.scene = QGraphicsScene()
        self.setScene(self.scene)
        #Font
        self.initFont()
        #Headers
        self.setHeads(self.heditor.pagesPerBlock)

        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setAlignment(Qt.AlignLeft)

    def initFont(self):
        self.font = QFont("Gothic")
        self.font.setFixedPitch(1)
        self.font.setBold(False)
        self.font.setPixelSize(14)

    def setHeads(self, pagesPerBlock):
        if self.heditor.pageOffView:
            self.setOffsetHead()
        else:
            self.setBlockHead()
        self.setPageHead(self.heditor.pagesPerBlock)

        linesize = 95 + (self.heditor.pagesPerBlock * (self.pagew + 2))
        #Decoration
        headLine = QGraphicsLineItem(QLineF(0, 20, linesize, 20))
        self.scene.addItem(headLine)
        headOffLine = QGraphicsLineItem(QLineF(90, 0, 90, 700))
        self.scene.addItem(headOffLine)


    def setOffsetHead(self):
        self.offHead = QGraphicsTextItem()
        self.offHead.setDefaultTextColor(QColor(Qt.red))
        self.offHead.setFont(self.font)
        self.offHead.setPlainText("Offset(Kb)")
        self.offHead.setPos(5, 0)
        self.scene.addItem(self.offHead)

    def setPageHead(self, len):
        count = 0
        x = 95
        while count < len:
            item = QGraphicsSimpleTextItem()
            item.setFont(self.font)
            item.setText("%.2x" % count)
            item.setPos(x, 3)
            self.scene.addItem(item)
            x += self.pagew + 2
            count += 1

    def setBlockHead(self):
        self.blockHead = QGraphicsTextItem()
        self.blockHead.setDefaultTextColor(QColor(Qt.red))
        self.blockHead.setFont(self.font)
        self.blockHead.setPlainText("Block")
        self.blockHead.setPos(15, 0)
        self.scene.addItem(self.blockHead)

    def initOffsetItems(self):
        count = 0
        x = 0
        y = 25
        while count <= self.displayLines:
            item = QGraphicsTextItem()
            item.setDefaultTextColor(QColor(Qt.red))
            item.setFont(self.font)
            item.setPos(x, y)
            self.offsetItems.append(item)
            y += self.pageh + 2
            count += 1
        #Add Items in scene
        for item in self.offsetItems:
            self.scene.addItem(item)

    def initPageItems(self):
        id = 0
        countpage = 0
        startx = 95
        starty = 25
        x = startx
        y = starty
        line = 0
        while line <= self.displayLines:
            while countpage < self.heditor.pagesPerBlock:
                p = page(x, y, id, self)
                self.pageItems.append(p)
                id += 1
#                self.scene.addItem(sec)
                x += self.pagew + 2
                countpage += 1
            x = startx
            y += self.pageh + 2
            countpage = 0
            line += 1
        #Add items in scene
        for item in self.pageItems:
            self.scene.addItem(item)

    def hidePageItems(self, startid):
        end = len(self.pageItems)
        for item in self.pageItems[startid:end]:
            item.setVisible(False)

    def setAllPageItemsVisible(self):
        for item in self.pageItems:
            item.setVisible(True)

    def hideOffsetItems(self, startid):
        end = len(self.offsetItems)
        for item in self.offsetItems[startid:end]:
            item.setVisible(False)

    def setAllOffsetItemsVisible(self):
        for item in self.offsetItems:
            item.setVisible(True)

    def appendOffsetItems(self, linesToAppend):
        count = 0
        x = 0
        y = 25 + (len(self.offsetItems) * (self.pageh + 2))
#        print "Y append offset ", y
        while count <= linesToAppend:
            item = QGraphicsTextItem()
            item.setDefaultTextColor(QColor(Qt.red))
            item.setFont(self.font)
            item.setPos(x, y)
            self.offsetItems.append(item)
            self.scene.addItem(item)
            y += self.pageh + 2
            count += 1

    def appendPageItems(self, linesToAppend):
        count = 0
        cp = 0
        x = 95
        y = 25 + ((len(self.pageItems) / self.heditor.pagesPerBlock) * (self.pageh + 2))
        id = len(self.pageItems)

        while count <= linesToAppend:
            while cp < self.heditor.pagesPerBlock:
                item = page(x, y, id, self)
                self.pageItems.append(item)
                self.scene.addItem(item)
                id += 1
                x += self.pagew + 2
                cp += 1
            count += 1
            x = 95
            y += self.pageh + 2

    def refreshOffsetItems(self, offset):
        #Check if end of pages or if number of pages < display pages
        self.setAllOffsetItemsVisible()

        block = (offset / self.heditor.pageSize) / self.heditor.pagesPerBlock
        startBlockOffset = block * (self.heditor.pagesPerBlock * self.heditor.pageSize)


        lineToDisplay = ((self.filesize - offset) / self.heditor.pageSize) / self.heditor.pagesPerBlock


        if ((self.filesize - offset) / self.heditor.pageSize) % self.heditor.pagesPerBlock > 0:
            lineToDisplay += 1

        if lineToDisplay >= self.displayLines:
            offset = startBlockOffset
            for item in self.offsetItems[0:self.displayLines]:
                if self.heditor.decimalview:
                    if self.heditor.pageOffView:
                        offlabel = QString("%.10d" % (offset / 1024))
                    else:
                        offlabel = QString("%.10d" % (offset / (self.heditor.pageSize * self.heditor.pagesPerBlock)))
                else:
                    if self.heditor.pageOffView:
                        offlabel = QString("%.10x" % (offset / 1024))
                    else:
                        offlabel = QString("%.10x" % (offset / (self.heditor.pageSize * self.heditor.pagesPerBlock)))
                item.setPlainText(offlabel)
                offset = offset + (self.heditor.pagesPerBlock * self.heditor.pageSize)
            self.heditor.startBlockOffset = startBlockOffset
        else:
            if lineToDisplay == 0:
                lineToDisplay = 5
                offset = startBlockOffset - (lineToDisplay * self.heditor.pagesPerBlock * self.heditor.pageSize)
                if ((self.filesize - offset) / self.heditor.pageSize) % self.heditor.pagesPerBlock > 0:
                    lineToDisplay += 1

                self.heditor.startBlockOffset = offset

            for item in self.offsetItems[0:lineToDisplay]:
                if self.heditor.decimalview:
                    if self.heditor.pageOffView:
                        offlabel = QString("%.10d" % (offset / 1024))
                    else:
                        offlabel = QString("%.10d" % (offset / (self.heditor.pageSize * self.heditor.pagesPerBlock)))
                else:
                    if self.heditor.pageOffView:
                        offlabel = QString("%.10x" % (offset / 1024))
                    else:
                        offlabel = QString("%.10x" % (offset / (self.heditor.pageSize * self.heditor.pagesPerBlock)))
                item.setPlainText(offlabel)
                offset = offset + (self.heditor.pagesPerBlock * self.heditor.pageSize)
            self.hideOffsetItems(lineToDisplay)

    def refreshPageItems(self, offset):
        self.setAllPageItemsVisible()
        maxpages = self.displayLines * self.heditor.pagesPerBlock
        displaypages = (self.filesize - offset) / self.heditor.pageSize

        if displaypages <= maxpages:
            if displaypages == 0:
                startline = self.lines - 5
                startOffset = startline * (self.heditor.pageSize * self.heditor.pagesPerBlock)
                rangeOffset = self.filesize - startOffset
                newdisplaypages = rangeOffset / self.heditor.pageSize
                if rangeOffset % self.heditor.pageSize > 0:
                    newdisplaypages += 1

                self.hidePageItems(newdisplaypages)
                self.heditor.startBlockOffset = startOffset
            else:
                rangeOffset = self.filesize - offset
                rangePages = rangeOffset / self.heditor.pageSize
                rangeLines = rangePages / self.heditor.pagesPerBlock
                newdisplaypages = rangeOffset / self.heditor.pageSize

                if rangeOffset % self.heditor.pageSize > 0:
                    newdisplaypages += 1
 
                self.hidePageItems(newdisplaypages)
                self.heditor.startBlockOffset = offset
        else:
            self.heditor.startBlockOffset = offset

        self.heditor.pageselection.update()

    def refreshAllContent(self):
        self.scene.clear()
        del self.offsetItems[:]
        del self.pageItems[:]

        self.setHeads(self.heditor.pagesPerBlock)
        self.initOffsetItems()
        self.initPageItems()
        self.refreshOffsetItems(self.heditor.startBlockOffset)
        self.refreshPageItems(self.heditor.startBlockOffset)

    def lineToOffset(self, line):
        offset = (line * (self.heditor.pagesPerBlock * self.heditor.pageSize))
        return offset
 
    def lineToOffsetKb(self, line):
        offset = (line * (self.heditor.pagesPerBlock * self.heditor.pageSize)) / 1024
        return offset

    def lineToOffsetMb(self, line):
        offset = ((line * (self.heditor.pagesPerBlock * self.heditor.pageSize)) / 1024) / 1024
        return offset

    def lineToOffsetGb(self, line):
        offset = (((line * (self.heditor.pagesPerBlock * self.heditor.pageSize)) / 1024) / 1024) / 1024
        return offset

############################
#       Colorize Pages     #
############################

    def refreshPagesColor(self, id):
#        print "Refresh: ", id
        self.cleanSelection()
        self.pageItems[id].setBrush(QBrush(Qt.green, Qt.SolidPattern))

    def cleanSelection(self):
        item = self.pageItems[self.selectedPage]
        item.setBrush(item.brush)


############################
#       Resize Event       #
############################

    def resizeEvent(self, sizEvent):
        y = sizEvent.size().height()
        disline = (y / self.pageh)
        #Start
        if self.start == True:
            if self.height() < self.heditor.mainWindow.height():
                if disline > self.displayLines:
                    self.displayLines = (y / self.pageh)
                    #Lines
                    self.lines = self.filesize / (self.heditor.pagesPerBlock * self.heditor.pageSize)
                    #Init
                    self.initOffsetItems()
                    self.initPageItems()
                    #Refresh
                    self.refreshOffsetItems(0)
                    self.refreshPageItems(0)

                    if self.lines > self.displayLines:
                        self.wpage.setScrollBar()
                    else:
                        self.wpage.scroll = False
                    self.start = False
        else:
            range = disline - self.displayLines
            if range > 0:
                self.displayLines = (y / self.pageh)
                #Append
                self.appendOffsetItems(range)
                self.appendPageItems(range)
                #Refresh
            self.refreshOffsetItems(self.heditor.startBlockOffset)
            self.refreshPageItems(self.heditor.startBlockOffset)
#            self.heditor.footer.pixel.view.resizeEvent(sizEvent)


############################
#       CallBacks          #
############################

    def wheelEvent(self, event):
        offset = self.heditor.startBlockOffset
#        up = False
        if self.wpage.scroll:
            if event.delta() > 0:
            #UP
                subOffset = 3 * self.heditor.pagesPerBlock * self.heditor.pageSize
                if offset - subOffset > 0:
                    offset = offset - subOffset
                else:
                    offset = 0
            else:
            #DOWN
                addOffset = 3 * self.heditor.pagesPerBlock * self.heditor.pageSize
                if offset + addOffset < self.filesize:
                    offset = offset + addOffset
                else:
                    offset = self.filesize - (5 * self.heditor.pagesPerBlock * self.heditor.pageSize)
        #Set ScrollBar value:
            if offset < self.filesize - (5 * self.heditor.pagesPerBlock * self.heditor.pageSize):
                value = self.wpage.scroll.offsetToValue(offset)
                self.wpage.scroll.setValue(value)
                
                self.refreshOffsetItems(offset)
                self.refreshPageItems(offset)


    def keyPressEvent(self, keyEvent):
	pass
        #if keyEvent.matches(QKeySequence.MoveToNextPage):
            #print "Next block"
        #elif keyEvent.matches(QKeySequence.MoveToPreviousPage):
            #print "Previous block"
        #elif keyEvent.matches(QKeySequence.MoveToNextLine):
            #print "Next Line"
        #elif keyEvent.matches(QKeySequence.MoveToPreviousLine):
            #print "Previous Line"
        #else:
            #pass

    def resetSelection(self):
        for item in self.pageItems:
            item.setBrush(item.brush)

    def getPageID(self, x, y):
        startx = 95
        starty = 25

        #Set scrollbar seek
        hscroll = self.horizontalScrollBar()
        if hscroll.value() > 0:
            startx -= hscroll.value()

        xrange = x - startx
        if xrange > 0:
            xcut = (xrange / (self.pagew + 2))
#            print xcut
        else:
            xcut = 0

        yrange = y - starty
        if yrange > 0:
            ycut = yrange / (self.pageh + 2)
        else:
            ycut = 0
        id = (ycut * self.heditor.pagesPerBlock) + xcut
        return id

    def mousePressEvent(self, event):
        button = event.button()
        pos = event.pos()
        if event.button() == 1:
            #Get CLicked coordonates
            x = pos.x()
            y = pos.y()
            id = self.getPageID(x, y)
            self.resetSelection()
            self.heditor.pageselection.select(id, self.heditor.startBlockOffset, 0)

    def mouseMoveEvent(self, event):
        pos = event.pos()
        x = pos.x()
        y = pos.y()
        if self.heditor.pageselection.mode == 1:
            id = self.getPageID(x, y)
            self.resetSelection()
            self.heditor.pageselection.select(id, self.heditor.startBlockOffset, 1)
#        else:
#            id = self.getPageID(x, y)
#            self.pageItems[id].setBrush(self)
        
    def mouseReleaseEvent(self, event):
        self.heditor.pageselection.mode = 0
示例#11
0
class EControlsGroup(QGraphicsPolygonItem):

    def __init__(self, parent=None, scene=None):
        super(EControlsGroup, self).__init__(parent, scene)

        self.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.setAcceptsHoverEvents(True)

        self.__kId = str( uuid.uuid1() )
        self.__name = QGraphicsTextItem()
        self.__name.setPlainText('EddGroup')

        self.__sWidth = 500
        self.__sHeight = 20
        self.__sExtra = 8
        self.__controlsOffset = 5

        self.__isDefaultPen = False

        self.__pens = { 0: EDraw.EColor.DefaultLeaveHoverPen, 1: EDraw.EColor.DefaultEnterHoverPen }
        self.setPen(self.__pens[self.__isDefaultPen])


        self.__shapeRect = QRectF( 0 , 0, self.__sWidth, self.__sHeight  )
        self.__controls = []

    def updateGeometry(self):

        adjustableControls = [ control for control in self.__controls if control.Adjustable ]

        if len(adjustableControls):
            fixedControlsBounds = self.__name.boundingRect().width()
            fixedControlsBounds += sum( map( int, [ control.boundingRect().width() for control in self.__controls if not control.Adjustable ] ))

            adjustableControlsWidth = int(( self.__sWidth - fixedControlsBounds )) / len(adjustableControls)

        step = self.__name.boundingRect().width()

        for control in self.__controls:
            if control.Adjustable:
                control.Width = adjustableControlsWidth - self.__controlsOffset

            control.setPos( step, 0.0 )
            step += control.boundingRect().width() + self.__controlsOffset

        self.__shapeRect = QRectF( 0 , -(self.__sExtra / 2), self.__sWidth, max([ control.Height for control in self.__controls ]) + self.__sExtra )

    @property
    def kId(self): return self.__kId

    @property
    def Name(self): return self.__name

    @Name.setter
    def Name(self, name ):
        " %s" % self.__name.setPlainText(name)

        self.updateGeometry()

    @property
    def Width(self): return self.__sWidth

    @Width.setter
    def Width(self, width):
        self.__sWidth = width #- self.__controlsOffset * len( self.__controls )
        self.updateGeometry()

    @property
    def Height(self): return self.__sHeight

    @property
    def Controls(self): return self.__controls

    @Controls.setter
    def Controls(self, control):
        control.setParentItem(self)
        self.__controls.append(control)
        self.updateGeometry()

    def toggleHighlight(self):
        if self.__isDefaultPen:
            self.__isDefaultPen = False
            self.setPen(self.__pens[self.__isDefaultPen])
            return

        self.__isDefaultPen = True
        self.setPen(self.__pens[self.__isDefaultPen])

    def hoverEnterEvent(self, mouseEvent):
        QGraphicsPolygonItem.hoverEnterEvent(self, mouseEvent)
        self.toggleHighlight()

    def hoverLeaveEvent(self, mouseEvent):
        QGraphicsPolygonItem.hoverLeaveEvent(self, mouseEvent)
        self.toggleHighlight()

    def shape(self): return QGraphicsItem.shape(self)

    def boundingRect(self):
        return self.__shapeRect

    def paint(self, painter, option, widget=None):


        painter.setPen(self.pen())
        painter.setBrush( EDraw.EColor.LinearGradient( self.boundingRect(), Qt.darkGray ) )
        painter.drawPolygon( QPolygonF( self.boundingRect() ) )

        painter.setPen(Qt.lightGray)

        r = QRectF( 0.0, -(self.__sExtra / 2), self.__name.boundingRect().width(), self.boundingRect().height() )
        painter.drawText( r , Qt.AlignRight | Qt.AlignCenter , "%s: " % self.__name.toPlainText() )
示例#12
0
class MyView(QGraphicsView):
    '''
    Näkymä.
    '''


    def __init__(self, kappalelista):
        '''
        Constructor
        '''
        super(MyView, self).__init__()
        self.kappalelista = kappalelista
        self.setMouseTracking(True)
        self.liitosOk = False
        self.tempLiitos = []
        self.tempVastin = []
        self.textItem = None

    def mousePressEvent(self, event):
        selected_items = self.scene().selectedItems()

        if not selected_items:
                currentItem = self.itemAt(event.pos())

                if currentItem is not None:
                    if currentItem.brush().color() == QColor(170,0,0) or currentItem.brush().color() == QColor(150,210,230):
                        currentItem = currentItem.parentItem()
                    currentItem.setSelected(True)
                    currentItem.parentItem().setZValue(1)
                    for liitoskohta in currentItem.parentItem().liitoskohdat:
                        if not liitoskohta.isFree():
                            liitoskohta.setFree()

        else:
            currentItem = selected_items[0]
            collision = False

            for anotherItem in self.kappalelista:
                if anotherItem.item != currentItem:
                    if currentItem.collidesWithItem(anotherItem.item,0x1):
                        collision = True

            if collision == False:
                currentItem.setSelected(False)
                currentItem.parentItem().setZValue(0)
                if self.liitosOk:
                    n = len(self.tempLiitokset)
                    for i in range(0,n):
                        self.tempLiitokset[i].liita(self.tempVastineet[i],0)

    def keyPressEvent(self, event):
        selected_items = self.scene().selectedItems()
        if selected_items:
            key = event.key()
            currentItem = selected_items[0]

            if key == 0x01000014: #Key_Right
                currentItem.parentItem().pyorita(5)

            if key == 0x01000012: #Key_Left
                currentItem.parentItem().pyorita(-5)

            if key == 0x01000007: #Delete
                currentItem.parentItem().poista()

    def rakenna(self):
        for currentItem in self.kappalelista:
            for anotherItem in self.kappalelista:
                if anotherItem != currentItem:
                    for liitoskohta in currentItem.liitoskohdat:
                        if liitoskohta.vapaa:
                            for vastakohta in anotherItem.liitoskohdat:
                                if vastakohta.vapaa:
                                    if liitoskohta.collidesWithItem(vastakohta,0x1):
                                        liitoskohta.liita(vastakohta,1)

    def LoopText(self):
        self.textItem = QGraphicsTextItem()
        self.textItem.setPlainText('Muodostui silmukka')
        self.scene().addItem(self.textItem)
        self.textItem.setZValue(2)
        self.textItem.setPos(200,0)

        fontti = QFont()
        fontti.setPointSize(40)

        self.textItem.setFont(fontti)

        timer = QTimer()
        timer.singleShot(3000,self.removeLoopText)

    def removeLoopText(self):
        self.scene().removeItem(self.textItem)
示例#13
0
class hexView(QGraphicsView):
    def __init__(self, parent):
        QGraphicsView.__init__(self, None, parent)
        self.init(parent)
        self.initShape()

    def init(self, parent):
        self.whex = parent
        self.heditor = self.whex.heditor
        #Init scene
        self.__scene = QGraphicsScene(self)
        self.setScene(self.__scene)

        #Get heditor stuff
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setAlignment(Qt.AlignLeft)

    def setItems(self):
        self.__scene.addItem(self.whex.offsetitem)
        self.__scene.addItem(self.whex.hexitem)
        self.__scene.addItem(self.whex.asciitem)

    def initShape(self):
        self.initHeads()
        #Line decoration
        offsetLine = QGraphicsLineItem(QLineF(90, 0, 90, 700))
        asciiLine = QGraphicsLineItem(QLineF(480, 0, 480, 700))
        #Add to scene
        self.__scene.addItem(offsetLine)
        self.__scene.addItem(asciiLine)

    def setCursors(self):
        self.__scene.addItem(self.whex.hexcursor)
        self.__scene.addItem(self.whex.asciicursor)

    def initHeads(self):
        self.offHead = QGraphicsTextItem()
        self.hexHead = QGraphicsTextItem()
        self.asciiHead = QGraphicsTextItem()
        #Set Color
        self.offHead.setDefaultTextColor(QColor(Qt.red))
        self.hexHead.setDefaultTextColor(QColor(Qt.black))
        self.asciiHead.setDefaultTextColor(QColor(Qt.darkCyan))
        #Create Font
        self.font = QFont("Gothic")
        self.font.setFixedPitch(1)
        self.font.setBold(False)
        self.font.setPixelSize(14)
        #Set Font
        self.offHead.setFont(self.font)
        self.hexHead.setFont(self.font)
        self.asciiHead.setFont(self.font)
        #Set Text
        self.offHead.setPlainText("Offset")
        self.hexHead.setPlainText(
            "0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F")
        self.asciiHead.setPlainText("Ascii")
        #Position
        self.offHead.setPos(20, 0)
        self.hexHead.setPos(95, 0)
        self.asciiHead.setPos(520, 0)
        #Add to scene
        self.__scene.addItem(self.offHead)
        self.__scene.addItem(self.hexHead)
        self.__scene.addItem(self.asciiHead)
        headLine = QGraphicsLineItem(QLineF(0, 20, 615, 20))
        self.__scene.addItem(headLine)

    def move(self, step, way):
        #step: line = 1 * bytesPerLine, page = pagesize, wheel = 3 * bytesPerLine
        offset = self.heditor.currentOffset
        if way == 0:
            #UP
            if (offset - (step * self.heditor.bytesPerLine)) >= 0:
                self.heditor.readOffset(offset -
                                        (step * self.heditor.bytesPerLine))
                if self.whex.isLFMOD():
                    self.whex.scroll.setValue(
                        self.whex.offsetToValue(offset - step *
                                                (self.heditor.bytesPerLine)))
                else:
                    self.whex.scroll.setValue(self.whex.scroll.value() - step)
            else:
                self.heditor.readOffset(0)
                self.whex.scroll.setValue(0)
        elif way == 1:
            #Down
            if (offset + (step * self.heditor.bytesPerLine)) <= (
                    self.heditor.filesize -
                (step * self.heditor.bytesPerLine)):
                self.heditor.readOffset(offset +
                                        (step * self.heditor.bytesPerLine))
                if self.whex.isLFMOD():
                    self.whex.scroll.setValue(
                        self.whex.offsetToValue(offset + step *
                                                (self.heditor.bytesPerLine)))
                else:
                    self.whex.scroll.setValue(self.whex.scroll.value() + step)
            else:
                self.heditor.readOffset(self.heditor.filesize - 5 *
                                        (self.heditor.bytesPerLine))
                self.whex.scroll.setValue(self.whex.scroll.max)

####################################
#        Navigation Operations     #
####################################

    def wheelEvent(self, event):
        offset = self.heditor.currentOffset
        if event.delta() > 0:
            self.move(3, 0)
        else:
            self.move(3, 1)

    def keyPressEvent(self, keyEvent):
        #        off = self.heditor.currentOffset
        if keyEvent.matches(QKeySequence.MoveToNextPage):
            self.move(self.heditor.pageSize / self.heditor.bytesPerLine, 1)
        elif keyEvent.matches(QKeySequence.MoveToPreviousPage):
            self.move(self.heditor.pageSize / self.heditor.bytesPerLine, 0)
        elif keyEvent.matches(QKeySequence.MoveToNextWord):
            print "Next Word"
        elif keyEvent.matches(QKeySequence.MoveToPreviousWord):
            print "Previous word"
        elif keyEvent.matches(QKeySequence.MoveToNextLine):
            print "Next Line"
        elif keyEvent.matches(QKeySequence.MoveToPreviousLine):
            print "Previous Line"
示例#14
0
class VennIntersectionArea(QGraphicsPathItem):
    def __init__(self, parent=None, text=""):
        super(QGraphicsPathItem, self).__init__(parent)
        self.setAcceptHoverEvents(True)
        self.setPen(QPen(Qt.NoPen))

        self.text = QGraphicsTextItem(self)
        layout = self.text.document().documentLayout()
        layout.documentSizeChanged.connect(self._onLayoutChanged)

        self._text = ""
        self._anchor = QPointF()

    def setText(self, text):
        if self._text != text:
            self._text = text
            self.text.setPlainText(text)

    def text(self):
        return self._text

    def setTextAnchor(self, pos):
        if self._anchor != pos:
            self._anchor = pos
            self._updateTextAnchor()

    def hoverEnterEvent(self, event):
        self.setZValue(self.zValue() + 1)
        return QGraphicsPathItem.hoverEnterEvent(self, event)

    def hoverLeaveEvent(self, event):
        self.setZValue(self.zValue() - 1)
        return QGraphicsPathItem.hoverLeaveEvent(self, event)

#     def mousePressEvent(self, event):
#         pos = event.pos()
#         parent = self.parentItem()
#         pbrect = parent.boundingRect()
#         w, h = pbrect.width(), pbrect.height()
#         print "(%.3f, %.3f)" % (pos.x() / w, pos.y() / h)
#         super(VennIntersectionArea, self).mousePressEvent(event)

    def paint(self, painter, option, widget=None):
        painter.save()
        path = self.path()
        brush = QBrush(self.brush())
        pen = QPen(self.pen())

        if option.state & QStyle.State_Selected:
            pen.setColor(Qt.red)
            brush.setStyle(Qt.DiagCrossPattern)
            brush.setColor(QColor(40, 40, 40, 100))

        elif option.state & QStyle.State_MouseOver:
            pen.setColor(Qt.blue)

        if option.state & QStyle.State_MouseOver:
            brush.setColor(QColor(100, 100, 100, 100))
            if brush.style() == Qt.NoBrush:
                # Make sure the highlight is actually visible.
                brush.setStyle(Qt.SolidPattern)

        painter.setPen(pen)
        painter.setBrush(brush)
        painter.drawPath(path)
        painter.restore()

    def itemChange(self, change, value):
        if change == QGraphicsPathItem.ItemSelectedHasChanged:
            if value.toBool():
                self.setZValue(self.zValue() + 1)
            else:
                self.setZValue(self.zValue() - 1)

        return QGraphicsPathItem.itemChange(self, change, value)

    def _updateTextAnchor(self):
        rect = self.text.boundingRect()
        pos = anchor_rect(rect, self._anchor)
        self.text.setPos(pos)

    def _onLayoutChanged(self):
        self._updateTextAnchor()
示例#15
0
class VennIntersectionArea(QGraphicsPathItem):
    def __init__(self, parent=None, text=""):
        super(QGraphicsPathItem, self).__init__(parent)
        self.setAcceptHoverEvents(True)
        self.setPen(QPen(Qt.NoPen))

        self.text = QGraphicsTextItem(self)
        layout = self.text.document().documentLayout()
        layout.documentSizeChanged.connect(self._onLayoutChanged)

        self._text = ""
        self._anchor = QPointF()

    def setText(self, text):
        if self._text != text:
            self._text = text
            self.text.setPlainText(text)

    def text(self):
        return self._text

    def setTextAnchor(self, pos):
        if self._anchor != pos:
            self._anchor = pos
            self._updateTextAnchor()

    def hoverEnterEvent(self, event):
        self.setZValue(self.zValue() + 1)
        return QGraphicsPathItem.hoverEnterEvent(self, event)

    def hoverLeaveEvent(self, event):
        self.setZValue(self.zValue() - 1)
        return QGraphicsPathItem.hoverLeaveEvent(self, event)

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            if event.modifiers() & Qt.AltModifier:
                self.setSelected(False)
            elif event.modifiers() & Qt.ControlModifier:
                self.setSelected(not self.isSelected())
            elif event.modifiers() & Qt.ShiftModifier:
                self.setSelected(True)
            else:
                for area in self.parentWidget().vennareas():
                    area.setSelected(False)
                self.setSelected(True)

    def mouseReleaseEvent(self, event):
        pass

    def paint(self, painter, option, widget=None):
        painter.save()
        path = self.path()
        brush = QBrush(self.brush())
        pen = QPen(self.pen())

        if option.state & QStyle.State_Selected:
            pen.setColor(Qt.red)
            brush.setStyle(Qt.DiagCrossPattern)
            brush.setColor(QColor(40, 40, 40, 100))

        elif option.state & QStyle.State_MouseOver:
            pen.setColor(Qt.blue)

        if option.state & QStyle.State_MouseOver:
            brush.setColor(QColor(100, 100, 100, 100))
            if brush.style() == Qt.NoBrush:
                # Make sure the highlight is actually visible.
                brush.setStyle(Qt.SolidPattern)

        painter.setPen(pen)
        painter.setBrush(brush)
        painter.drawPath(path)
        painter.restore()

    def itemChange(self, change, value):
        if change == QGraphicsPathItem.ItemSelectedHasChanged:
            self.setZValue(self.zValue() + (1 if value else -1))
        return QGraphicsPathItem.itemChange(self, change, value)

    def _updateTextAnchor(self):
        rect = self.text.boundingRect()
        pos = anchor_rect(rect, self._anchor)
        self.text.setPos(pos)

    def _onLayoutChanged(self):
        self._updateTextAnchor()
示例#16
0
class LinkItem(QGraphicsObject):
    """
    A Link item in the canvas that connects two :class:`.NodeItem`\s in the
    canvas.

    The link curve connects two `Anchor` items (see :func:`setSourceItem`
    and :func:`setSinkItem`). Once the anchors are set the curve
    automatically adjusts its end points whenever the anchors move.

    An optional source/sink text item can be displayed above the curve's
    central point (:func:`setSourceName`, :func:`setSinkName`)

    """

    #: Z value of the item
    Z_VALUE = 0

    def __init__(self, *args):
        QGraphicsObject.__init__(self, *args)
        self.setFlag(QGraphicsItem.ItemHasNoContents, True)
        self.setAcceptedMouseButtons(Qt.RightButton | Qt.LeftButton)
        self.setAcceptHoverEvents(True)

        self.setZValue(self.Z_VALUE)

        self.sourceItem = None
        self.sourceAnchor = None
        self.sinkItem = None
        self.sinkAnchor = None

        self.curveItem = LinkCurveItem(self)

        self.sourceIndicator = LinkAnchorIndicator(self)
        self.sinkIndicator = LinkAnchorIndicator(self)
        self.sourceIndicator.hide()
        self.sinkIndicator.hide()

        self.linkTextItem = QGraphicsTextItem(self)

        self.__sourceName = ""
        self.__sinkName = ""

        self.__dynamic = False
        self.__dynamicEnabled = False

        self.hover = False

    def setSourceItem(self, item, anchor=None):
        """
        Set the source `item` (:class:`.NodeItem`). Use `anchor`
        (:class:`.AnchorPoint`) as the curve start point (if ``None`` a new
        output anchor will be created using ``item.newOutputAnchor()``).

        Setting item to ``None`` and a valid anchor is a valid operation
        (for instance while mouse dragging one end of the link).

        """
        if item is not None and anchor is not None:
            if anchor not in item.outputAnchors():
                raise ValueError("Anchor must be belong to the item")

        if self.sourceItem != item:
            if self.sourceAnchor:
                # Remove a previous source item and the corresponding anchor
                self.sourceAnchor.scenePositionChanged.disconnect(
                    self._sourcePosChanged)

                if self.sourceItem is not None:
                    self.sourceItem.removeOutputAnchor(self.sourceAnchor)

                self.sourceItem = self.sourceAnchor = None

            self.sourceItem = item

            if item is not None and anchor is None:
                # Create a new output anchor for the item if none is provided.
                anchor = item.newOutputAnchor()

            # Update the visibility of the start point indicator.
            self.sourceIndicator.setVisible(bool(item))

        if anchor != self.sourceAnchor:
            if self.sourceAnchor is not None:
                self.sourceAnchor.scenePositionChanged.disconnect(
                    self._sourcePosChanged)

            self.sourceAnchor = anchor

            if self.sourceAnchor is not None:
                self.sourceAnchor.scenePositionChanged.connect(
                    self._sourcePosChanged)

        self.__updateCurve()

    def setSinkItem(self, item, anchor=None):
        """
        Set the sink `item` (:class:`.NodeItem`). Use `anchor`
        (:class:`.AnchorPoint`) as the curve end point (if ``None`` a new
        input anchor will be created using ``item.newInputAnchor()``).

        Setting item to ``None`` and a valid anchor is a valid operation
        (for instance while mouse dragging one and of the link).

        """
        if item is not None and anchor is not None:
            if anchor not in item.inputAnchors():
                raise ValueError("Anchor must be belong to the item")

        if self.sinkItem != item:
            if self.sinkAnchor:
                # Remove a previous source item and the corresponding anchor
                self.sinkAnchor.scenePositionChanged.disconnect(
                    self._sinkPosChanged)

                if self.sinkItem is not None:
                    self.sinkItem.removeInputAnchor(self.sinkAnchor)

                self.sinkItem = self.sinkAnchor = None

            self.sinkItem = item

            if item is not None and anchor is None:
                # Create a new input anchor for the item if none is provided.
                anchor = item.newInputAnchor()

            # Update the visibility of the end point indicator.
            self.sinkIndicator.setVisible(bool(item))

        if self.sinkAnchor != anchor:
            if self.sinkAnchor is not None:
                self.sinkAnchor.scenePositionChanged.disconnect(
                    self._sinkPosChanged)

            self.sinkAnchor = anchor

            if self.sinkAnchor is not None:
                self.sinkAnchor.scenePositionChanged.connect(
                    self._sinkPosChanged)

        self.__updateCurve()

    def setFont(self, font):
        """
        Set the font for the channel names text item.
        """
        if font != self.font():
            self.linkTextItem.setFont(font)
            self.__updateText()

    def font(self):
        """
        Return the font for the channel names text.
        """
        return self.linkTextItem.font()

    def setChannelNamesVisible(self, visible):
        """
        Set the visibility of the channel name text.
        """
        self.linkTextItem.setVisible(visible)

    def setSourceName(self, name):
        """
        Set the name of the source (used in channel name text).
        """
        if self.__sourceName != name:
            self.__sourceName = name
            self.__updateText()

    def sourceName(self):
        """
        Return the source name.
        """
        return self.__sourceName

    def setSinkName(self, name):
        """
        Set the name of the sink (used in channel name text).
        """
        if self.__sinkName != name:
            self.__sinkName = name
            self.__updateText()

    def sinkName(self):
        """
        Return the sink name.
        """
        return self.__sinkName

    def _sinkPosChanged(self, *arg):
        self.__updateCurve()

    def _sourcePosChanged(self, *arg):
        self.__updateCurve()

    def __updateCurve(self):
        self.prepareGeometryChange()
        if self.sourceAnchor and self.sinkAnchor:
            source_pos = self.sourceAnchor.anchorScenePos()
            sink_pos = self.sinkAnchor.anchorScenePos()
            source_pos = self.curveItem.mapFromScene(source_pos)
            sink_pos = self.curveItem.mapFromScene(sink_pos)

            # Adaptive offset for the curve control points to avoid a
            # cusp when the two points have the same y coordinate
            # and are close together
            delta = source_pos - sink_pos
            dist = math.sqrt(delta.x()**2 + delta.y()**2)
            cp_offset = min(dist / 2.0, 60.0)

            # TODO: make the curve tangent orthogonal to the anchors path.
            path = QPainterPath()
            path.moveTo(source_pos)
            path.cubicTo(source_pos + QPointF(cp_offset, 0),
                         sink_pos - QPointF(cp_offset, 0), sink_pos)

            self.curveItem.setPath(path)
            self.sourceIndicator.setPos(source_pos)
            self.sinkIndicator.setPos(sink_pos)
            self.__updateText()
        else:
            self.setHoverState(False)
            self.curveItem.setPath(QPainterPath())

    def __updateText(self):
        self.prepareGeometryChange()

        if self.__sourceName or self.__sinkName:
            if self.__sourceName != self.__sinkName:
                text = u"{0} \u2192 {1}".format(self.__sourceName,
                                                self.__sinkName)
            else:
                # If the names are the same show only one.
                # Is this right? If the sink has two input channels of the
                # same type having the name on the link help elucidate
                # the scheme.
                text = self.__sourceName
        else:
            text = ""

        self.linkTextItem.setPlainText(text)

        path = self.curveItem.path()
        if not path.isEmpty():
            center = path.pointAtPercent(0.5)
            angle = path.angleAtPercent(0.5)

            brect = self.linkTextItem.boundingRect()

            transform = QTransform()
            transform.translate(center.x(), center.y())
            transform.rotate(-angle)

            # Center and move above the curve path.
            transform.translate(-brect.width() / 2, -brect.height())

            self.linkTextItem.setTransform(transform)

    def removeLink(self):
        self.setSinkItem(None)
        self.setSourceItem(None)
        self.__updateCurve()

    def setHoverState(self, state):
        if self.hover != state:
            self.prepareGeometryChange()
            self.hover = state
            self.sinkIndicator.setHoverState(state)
            self.sourceIndicator.setHoverState(state)
            self.curveItem.setHoverState(state)

    def hoverEnterEvent(self, event):
        # Hover enter event happens when the mouse enters any child object
        # but we only want to show the 'hovered' shadow when the mouse
        # is over the 'curveItem', so we install self as an event filter
        # on the LinkCurveItem and listen to its hover events.
        self.curveItem.installSceneEventFilter(self)
        return QGraphicsObject.hoverEnterEvent(self, event)

    def hoverLeaveEvent(self, event):
        # Remove the event filter to prevent unnecessary work in
        # scene event filter when not needed
        self.curveItem.removeSceneEventFilter(self)
        return QGraphicsObject.hoverLeaveEvent(self, event)

    def sceneEventFilter(self, obj, event):
        if obj is self.curveItem:
            if event.type() == QEvent.GraphicsSceneHoverEnter:
                self.setHoverState(True)
            elif event.type() == QEvent.GraphicsSceneHoverLeave:
                self.setHoverState(False)

        return QGraphicsObject.sceneEventFilter(self, obj, event)

    def boundingRect(self):
        return self.childrenBoundingRect()

    def shape(self):
        return self.curveItem.shape()

    def setEnabled(self, enabled):
        """
        Reimplemented from :class:`QGraphicsObject`

        Set link enabled state. When disabled the link is rendered with a
        dashed line.

        """
        # This getter/setter pair override a property from the base class.
        # They should be renamed to e.g. setLinkEnabled/linkEnabled
        self.curveItem.setLinkEnabled(enabled)

    def isEnabled(self):
        return self.curveItem.isLinkEnabled()

    def setDynamicEnabled(self, enabled):
        """
        Set the link's dynamic enabled state.

        If the link is `dynamic` it will be rendered in red/green color
        respectively depending on the state of the dynamic enabled state.

        """
        if self.__dynamicEnabled != enabled:
            self.__dynamicEnabled = enabled
            if self.__dynamic:
                self.__updatePen()

    def isDynamicEnabled(self):
        """
        Is the link dynamic enabled.
        """
        return self.__dynamicEnabled

    def setDynamic(self, dynamic):
        """
        Mark the link as dynamic (i.e. it responds to
        :func:`setDynamicEnabled`).

        """
        if self.__dynamic != dynamic:
            self.__dynamic = dynamic
            self.__updatePen()

    def isDynamic(self):
        """
        Is the link dynamic.
        """
        return self.__dynamic

    def __updatePen(self):
        self.prepareGeometryChange()
        if self.__dynamic:
            if self.__dynamicEnabled:
                color = QColor(0, 150, 0, 150)
            else:
                color = QColor(150, 0, 0, 150)

            normal = QPen(QBrush(color), 2.0)
            hover = QPen(QBrush(color.darker(120)), 2.1)
        else:
            normal = QPen(QBrush(QColor("#9CACB4")), 2.0)
            hover = QPen(QBrush(QColor("#7D7D7D")), 2.1)

        self.curveItem.setCurvePenSet(normal, hover)
示例#17
0
class EFrameLayout(QGraphicsPolygonItem):

    def __init__(self, parent=None, scene=None):
        super(EFrameLayout, self).__init__(parent, scene)

        self.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.setAcceptsHoverEvents(True)

        self.__name = QGraphicsTextItem()
        self.__name.setPlainText('Frame Layout')

        self.__handleWidth = 500
        self.__handleHeight = 20
        self.__separator = 5
        self.__boundExtra = 3 + self.pen().width()
        self.__handleRect = QRectF( 0.0 , 0.0 , self.__handleWidth, self.__handleHeight  )

        self.__controlsBound = 0.0
        self.__controls = []

        self.__isDefaultPen = False
        self.__isCollapsed = True

        self.__pens = { 0: EDraw.EColor.DefaultLeaveHoverPen, 1: EDraw.EColor.DefaultEnterHoverPen }
        self.setPen(self.__pens[self.__isDefaultPen])

    @property
    def Label(self): return self.__name.toPlainText()

    @Label.setter
    def Label(self, label): self.__name.setPlainText( str( label ) )

    @property
    def Width(self): return self.__handleWidth

    @Width.setter
    def Width(self, width):
        self.__handleWidth = width
        self.updateGeometry()

    def toggleContentVisibility(self):
        if not len(self.__controls): return

        if self.__controls[0].isVisible():
            [ control.hide() for control in self.__controls ]
            self.__isCollapsed = True
            return

        [ control.show() for control in self.__controls ]
        self.__isCollapsed = False

    def updateGeometry(self):

        self.__handleRect = QRectF( 0.0 , 0.0 , self.__handleWidth, self.__handleHeight  )

        if not len(self.__controls) or self.__isCollapsed:
            self.__controlsBound = 0.0
            return

        self.__controlsBound = 0.0
        self.__controlsBound = self.__separator
        self.__controlsBound += sum( [ control.boundingRect().height() + self.__separator for control in self.__controls ] )

        step = self.__handleHeight + self.__separator * 2

        for control in self.__controls:

            control.Name.setTextWidth( self.__controlNameWidth )
            control.Width = self.__handleRect.normalized().adjusted( 5.0, 0.0, -5.0, 0.0 ).width()

            control.setPos( 5.0 , step )
            step += control.boundingRect().height() + self.__separator

    def addControl(self, control):
        if not isinstance(control, EControlsGroup): raise AttributeError

        self.__controls.append(control)
        control.setParentItem(self)
        control.setFlag(QGraphicsItem.ItemIsMovable, False)

        self.__controlNameWidth = max([ control.Name.boundingRect().width() for control in self.__controls ]) + 5

        self.__isCollapsed = False
        self.updateGeometry()

    def clear(self):
        [ control.setParentItem(None) for control in self.__controls ]
        self.__controls = []

        self.updateGeometry()

    def mouseDoubleClickEvent(self, mouseEvent):
        QGraphicsPolygonItem.mouseDoubleClickEvent(self, mouseEvent)

        self.toggleContentVisibility()
        self.updateGeometry()

    def shape(self): return QGraphicsItem.shape(self)

    def boundingRect(self):
        return self.__handleRect.normalized().adjusted( 0.0, 0.0, 0.0, self.__controlsBound )

    def paint(self, painter, option, widget=None):

        if not self.__isCollapsed:
            painter.setPen( QPen( QColor( 0, 0, 0, 50 ), 2 , Qt.SolidLine ) )
            painter.setBrush( QColor( 0, 0, 0, 50 ) )
            painter.drawRect( self.boundingRect().adjusted( self.pen().width(), self.__handleHeight , -self.pen().width(), 0.0 ) )

        painter.setPen(self.pen())
        painter.setBrush( EDraw.EColor.LinearGradient( self.__handleRect, Qt.darkGray ) )
        #painter.drawPolygon( QPolygonF( self.__handleRect.normalized().adjusted(-self.__boundExtra, 0.0, self.__boundExtra, 0.0 ) ) )
        painter.drawPolygon( QPolygonF( self.__handleRect ) )

        painter.setPen(Qt.lightGray)
        r = QRectF( 0.0, 0.0, self.__name.boundingRect().width(), self.__handleRect.height() )
        painter.drawText( r, Qt.AlignCenter, self.__name.toPlainText() )
示例#18
0
文件: hexView.py 项目: halbbob/dff
class hexView(QGraphicsView):
    def __init__(self, parent):
        QGraphicsView.__init__(self)
        self.init(parent)
        self.initShape()

    def init(self, parent):
        self.whex = parent
        self.bdiff = self.whex.bdiff
        #Init scene
        self.scene = QGraphicsScene()
        self.setScene(self.scene)
        #Get bdiff stuff
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setAlignment(Qt.AlignLeft)

    def setItems(self):
        self.scene.addItem(self.whex.offsetitem)
        self.scene.addItem(self.whex.hexitem)
        self.scene.addItem(self.whex.asciitem)

    def initShape(self):
        self.initHeads()
        #Line decoration
        offsetLine = QGraphicsLineItem(QLineF(90, 0, 90, 700))
        asciiLine = QGraphicsLineItem(QLineF(480, 0, 480, 700))
        #Add to scene
        self.scene.addItem(offsetLine)
        self.scene.addItem(asciiLine)

    def initHeads(self):
        self.offHead = QGraphicsTextItem()
        self.hexHead = QGraphicsTextItem()
        self.asciiHead = QGraphicsTextItem()
        #Set Color
        self.offHead.setDefaultTextColor(QColor(Qt.red))
        self.hexHead.setDefaultTextColor(QColor(Qt.black))
        self.asciiHead.setDefaultTextColor(QColor(Qt.darkCyan))
        #Create Font
        self.font = QFont("Gothic")
        self.font.setFixedPitch(1)
        self.font.setBold(False)
        self.font.setPixelSize(14)
        #Set Font
        self.offHead.setFont(self.font)
        self.hexHead.setFont(self.font)
        self.asciiHead.setFont(self.font)
        #Set Text
        self.offHead.setPlainText("Offset")
        self.hexHead.setPlainText("0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F")
        self.asciiHead.setPlainText("Ascii")
        #Position
        self.offHead.setPos(20, 0)
        self.hexHead.setPos(95, 0)
        self.asciiHead.setPos(520, 0)
        #Add to scene
        self.scene.addItem(self.offHead)
        self.scene.addItem(self.hexHead)
        self.scene.addItem(self.asciiHead)
        headLine = QGraphicsLineItem(QLineF(0, 20, 615, 20))
        self.scene.addItem(headLine)

    def setSyncView(self, whexview):
        self.whexviewsync = whexview

    def move(self, step, way):
        #step: line = 1 * bytesPerLine, page = pagesize, wheel = 3 * bytesPerLine
        offset = self.bdiff.currentOffset
#        print offset
        if way == 0:
        #UP
            if (offset - (step * self.bdiff.bytesPerLine)) >= 0:
                self.bdiff.readOffset(offset - (step * self.bdiff.bytesPerLine))
                if self.bdiff.scrollbar.isLFMOD():
                    self.bdiff.scrollbar.setValue(self.bdiff.scrollbar.offsetToValue(offset - (step * self.bdiff.bytesPerLine)))
                else:
                    self.bdiff.scrollbar.setValue(self.bdiff.scrollbar.value() - step)
            else:
                self.bdiff.readOffset(0)
                self.bdiff.scrollbar.setValue(0)
        elif way == 1:
       #Down
            if (offset + (step * self.bdiff.bytesPerLine)) <= (self.bdiff.masterFileSize - (step * self.bdiff.bytesPerLine)):
                self.bdiff.readOffset(offset + (step * self.bdiff.bytesPerLine))
                if self.bdiff.scrollbar.isLFMOD():
                    self.bdiff.scrollbar.setValue(self.bdiff.scrollbar.offsetToValue(offset + (step * self.bdiff.bytesPerLine)))
                else:
                    self.bdiff.scrollbar.setValue(self.bdiff.scrollbar.value() + step)
            else:
                self.bdiff.readOffset(self.bdiff.masterFileSize - 5 * (self.bdiff.bytesPerLine))             
                self.bdiff.scrollbar.setValue(self.bdiff.scrollbar.max)


####################################
#        Navigation Operations     #
####################################

    def wheelEvent(self, event):
        offset = self.bdiff.currentOffset
        if event.delta() > 0:
            self.move(3, 0)
            self.whexviewsync.move(3, 0)
        else:
            self.move(3, 1)
            self.whexviewsync.move(3, 1)


    def keyPressEvent(self, keyEvent):
        off = self.bdiff.currentOffset
        if keyEvent.matches(QKeySequence.MoveToNextPage):
            self.move(self.bdiff.pageSize / self.bdiff.bytesPerLine, 1)
            self.whexviewsync.move(self.bdiff.pageSize / self.bdiff.bytesPerLine, 1)
        elif keyEvent.matches(QKeySequence.MoveToPreviousPage):
            self.move(self.bdiff.pageSize / self.bdiff.bytesPerLine, 0)
            self.whexviewsync.move(self.bdiff.pageSize / self.bdiff.bytesPerLine, 0)
        elif keyEvent.matches(QKeySequence.MoveToNextLine):
            self.move(1, 1)
            self.whexviewsync.move(1, 1)
        elif keyEvent.matches(QKeySequence.MoveToPreviousLine):
            self.move(1, 0)
            self.whexviewsync.move(1, 0)
示例#19
0
class ClassModel(QGraphicsItem):
    def __init__(self, parent=None, graphicView=None, graphicScene=None):
        QGraphicsItem.__init__(self)
        self.set_default_data()
        self.className = QGraphicsTextItem(self)
        self.functionsItem = FunctionsContainerModel(self)
        self.className.setPlainText(self.defaultClassName)
        self.setFlag(self.ItemIsMovable)
        self.setFlag(self.ItemSendsGeometryChanges)
        self.functionsItem.setPos(0, self.__get_title_height())
        self.attributesItem = FunctionsContainerModel(self)
        self.attributesItem.setPos(0, self.functionsItem.get_height())

    def set_default_data(self):
        self.maxWidth = 100
        self.defaultClassNameHeight = 30
        self.defaultClassName = "No name"

    def set_functions_list(self, functionsList):
        self.functionsItem.set_functions_list(functionsList, "*", "()")
        self.update_positions()

    def set_attributes_list(self, attributesList):
        self.attributesItem.set_functions_list(attributesList)
        self.update_positions()

    def set_class_name(self, className):
        self.className.setPlainText(className)

    def _get_width(self):
        self.__calc_max_width()
        return self.maxWidth

    def __get_title_height(self):
        titleHeight = self.defaultClassNameHeight
        if titleHeight == self.className.document().size().height():
            titleHeight = self.className.document().size().height()
        return titleHeight

    def get_height(self):
        summary = self.defaultClassNameHeight
        summary += self.functionsItem.get_height()
        summary += self.attributesItem.get_height()
        return summary

    def __calc_max_width(self):
        if self.maxWidth < self.className.document().size().width():
            self.maxWidth = self.className.document().size().width()
        if hasattr(self, "functionsItem"):
            if self.maxWidth < self.functionsItem.get_width():
                self.maxWidth = self.functionsItem.get_width()
        if hasattr(self, "attributesItem"):
            if self.maxWidth < self.attributesItem.get_width():
                self.maxWidth = self.attributesItem.get_width()

    def set_bg_color(self, qColor):
        self.backgroundColor = qColor

    def set_method_list(self, itemList):
        self.methodList = itemList

    def update_positions(self):
        self.functionsItem.setPos(0, self.__get_title_height())
        self.attributesItem.setPos(
            0,
            self.functionsItem.y() + self.functionsItem.get_height())

    def paint(self, painter, option, widget):
        gradient = QRadialGradient(-3, -3, 10)
        if option.state & QStyle.State_Sunken:
            gradient.setCenter(3, 3)
            gradient.setFocalPoint(3, 3)
            gradient.setColorAt(0, QColor(Qt.yellow).light(120))
        else:
            gradient.setColorAt(0, QColor(Qt.yellow).light(120))
        painter.setBrush(gradient)
        painter.setPen(QPen(Qt.black, 0))
        painter.drawRoundedRect(self.boundingRect(), 3, 3)

    def boundingRect(self):
        return QRectF(0, 0, self._get_width(), self.get_height())

    def add_edge(self, edge):
        self.myEdge = edge
        edge.adjust()
示例#20
0
文件: Canvas1.py 项目: iras/JADE
class CanvasProps (QGraphicsItem):


    def __init__(self, parent=None, scene=None):
        
        QGraphicsItem.__init__ (self)
        
        self.parent = parent
        self.helper = self.parent.getHelper()
        
        #self.setFlags (QGraphicsItem.ItemIsSelectable)
        self.setAcceptsHoverEvents (True)
        
        self.pen_color = QPen (Qt.black, 2)
        
        self.color = QColor (Qt.white).dark (150)
        
        # init Canvas Animation Tweening
        self.timeline = QTimeLine (200)
        self.timeline.setFrameRange (0, 100)
        self.anim = QGraphicsItemAnimation ()
        self.anim.setItem (self)
        self.anim.setTimeLine (self.timeline)
        self.helper.connect (self.timeline, SIGNAL("finished()"), self.moveFurtherUp)
        self.anim_active = False
        
        #self._nodename = QGraphicsTextItem ('text '+str(self.node_id), self)
        self._nodename = QGraphicsTextItem ('', self)
        self._nodename.setPos (QPointF (18, 20))
        self._nodename.setDefaultTextColor (QColor (Qt.white).light (255))
        self._nodename.setFont (QFont ("Helvetica", 11, QFont.Bold, False))
        self._nodename.setTextWidth(120)
        self._nodename.setToolTip (self._nodename.toPlainText ())
        #self._nodename.setHtml("<h2 align=\"center\">hello</h2><h2 align=\"center\">world 1234345345</h2>123");
        
        self.props_list = []
        self.props_textItem_value_list = []
        
        self.FACTOR = 4.0
        
        self._canvas_height = 0
    
    def boundingRect (self): return QRectF (0, 0, 122, 150)
    
    def shape (self):
        
        path = QPainterPath ()
        path.addRect (0, 0, 122, 20)
        return path
    
    def paint (self, painter, option, unused_widget):
        
        if option.state & QStyle.State_Selected:
            fillColor = self.color.dark (100)
        else:
            fillColor = self.color
        
        if option.state & QStyle.State_MouseOver:
            fillColor = fillColor.light (120)
        
        if option.levelOfDetail < 0.2:
            
            if option.levelOfDetail < 0.125:
                painter.fillRect (QRectF (0, 0, 110, 70), fillColor)
                return
            
            painter.setPen   (QPen (Qt.black, 0))
            painter.setBrush (fillColor)
            painter.drawRect (0, 0, 120, 20)
            return
        
        oldPen = painter.pen ()
        pen = oldPen
        width = 0
        
        if option.state & QStyle.State_Selected:
            width += 2
        
        pen.setWidth (width)
        if option.state & QStyle.State_Sunken:
            level = 120
        else:
            level = 100
        
        painter.setBrush (QBrush (fillColor.dark (level)))
        #painter.drawRoundRect (QRect (0, 0, 80, 34+self.height), 20)
        painter.drawRect (QRect (0, 20, 120, 30+9*self._canvas_height))
    
    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    
    def addProp (self, prop_name, prop_value):
        
        i = len (self.props_list)
        self.props_list.append  (QGraphicsTextItem (prop_name + ' : ', self))
        self.props_textItem_value_list.append (CustomFloatingText (prop_value, self))
        
        # (1) adding the prop's name.
        self.props_list[i].setPos (QPointF (7, 35+i*10))
        self.props_list[i].setDefaultTextColor (QColor (Qt.white).light (255))
        self.props_list[i].setFont (QFont ("Helvetica", 9, QFont.StyleItalic, False))
        self.props_list[i].setTextWidth (55)
        self.props_list[i].setToolTip (self.props_list[i].toPlainText ())
        
        # (2) adding the prop's value.
        self.props_textItem_value_list[i].setTextInteractionFlags (Qt.TextEditable)
        self.props_textItem_value_list[i].setPos (QPointF (55, 35+i*10))
        self.props_textItem_value_list[i].setDefaultTextColor (QColor (Qt.white).light (255))
        self.props_textItem_value_list[i].setFont (QFont ("Helvetica", 9, QFont.StyleNormal, False))
        self.props_textItem_value_list[i].setTextWidth (55)
        
        receiver = lambda value: self.parent.listenToChangedPropsValues (prop_name, value)
        self.helper.connect (self.props_textItem_value_list[i], SIGNAL ("textChanged(QString)"), receiver)
    
    def getProps (self):
        
        tmp_list = []
        
        l = len (self.props_list)
        for i in range (0, l):
            tmp_list[i] = [self.props_list[i].toPlainText(), self.props_textItem_value_list[i].toPlainText()]
        
        return tmp_list
    
    def setTitle (self, title): self._nodename.setPlainText (title)
    
    def setCanvasHeightInUnits (self, ch): self._canvas_height = ch
    
    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    
    def moveDown (self, canvas_height_in_units):
        
        self.anim_active  = True
        self.upwards_flag = False
        
        self.canvas_height = canvas_height_in_units
        
        self.timeline.stop ()
        
        self.anim.setPosAt (0, QPointF(0, 1+(self.canvas_height+1)*self.FACTOR))
        self.anim.setPosAt (1, QPointF(0, 1+(self.canvas_height+2)*self.FACTOR))
        
        self.timeline.start ()
        self.update ()
    
    def moveUp (self, canvas_height_in_units):
        
        if self.anim_active == False:
            
            self.anim_active  = True
            self.upwards_flag = True
            
            self.canvas_height = canvas_height_in_units
            
            self.timeline.stop ()
            
            self.anim.setPosAt (0, QPointF(0, 1+(self.canvas_height+1)*self.FACTOR))
            self.anim.setPosAt (1, QPointF(0, 1+(self.canvas_height)  *self.FACTOR))
            
            self.timeline.start ()
            self.update ()
    
    # this method double-checks whether the canvas needs to be further up as a
    # result of receiving other asynchronous "delete link" SIGNALs while moving up.
    def moveFurtherUp (self):
        
        self.anim_active = False
        
        if self.upwards_flag==True:
            
            if self.parent.getMaxLen() < self.canvas_height:
                self.moveUp (self.canvas_height-1)
    
    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    
    def hoverEnterEvent (self, e):
        
        self._nodename.setToolTip (self._nodename.toPlainText ())
        QGraphicsItem.hoverEnterEvent (self, e)
    
    '''
示例#21
0
class XNodeConnection( QGraphicsPathItem ):
    """ 
    Defines the base graphics item class that is used to draw a connection
    between two nodes.
    """
    def __init__( self, scene ):
        self._visible               = True
        
        super(XNodeConnection, self).__init__()
        
        # define custom properties
        self._textItem              = None
        self._polygons              = []
        self._style                 = XConnectionStyle.Linear
        self._padding               = 20
        self._squashThreshold       = 2 * scene.cellWidth()
        self._showDirectionArrow    = False
        self._highlightPen          = QPen(QColor('yellow'))
        self._disabledPen           = QPen(QColor(100, 100, 100))
        self._disableWithLayer      = False
        self._enabled               = True
        self._dirty                 = True
        self._customData            = {}
        self._layer                 = None
        self._font                  = QApplication.instance().font()
        self._text                  = ''
        
        self._inputNode                     = None
        self._inputFixedY                   = None
        self._inputFixedX                   = None
        self._inputPoint                    = QPointF()
        self._inputLocation                 = XConnectionLocation.Left
        self._autoCalculateInputLocation    = False
        self._showInputArrow                = False
        
        self._outputNode                    = None
        self._outputFixedX                  = None
        self._outputFixedY                  = None
        self._outputPoint                   = QPointF()
        self._outputLocation                = XConnectionLocation.Right
        self._autoCalculateOutputLocation   = False
        self._showOutputArrow               = False
        
        # set standard properties
        self.setFlags( self.ItemIsSelectable )
        self.setZValue(-1)
        self.setPen( QColor('white') )
        self.setLayer( scene.currentLayer() )
        
    def autoCalculateInputLocation( self ):
        """
        :remarks    Returns whether or not to auto calculate the input
                    location based on the proximity to the output node
                    or point.
        
        :return     <bool>
        """
        return self._autoCalculateInputLocation
    
    def autoCalculateOutputLocation( self ):
        """
        :remarks    Returns whether or not to auto calculate the input
                    location based on the proximity to the output node
                    or point.
        
        :return     <bool>
        """
        return self._autoCalculateOutputLocation
    
    def connectSignals( self, node ):
        """
        :remarks    Connects to signals of the inputed node, if the node
                    is a valid XNode type.
        
        :param      node    <XNode> || None
        
        :return     <bool> success
        """
        from projexui.widgets.xnodewidget.xnode import XNode
        
        # make sure we're connecting to a valid node
        if ( not isinstance(node, XNode) ):
            return False
        
        node.dispatch.geometryChanged.connect(  self.setDirty )
        node.dispatch.visibilityChanged.connect(self.setDirty)
        node.dispatch.removed.connect(          self.forceRemove )
        return True
    
    def controlPoints( self ):
        """
        :remarks    Generates the control points for this path
        
        :return     <list> [ <tuple> ( <float> x, <float> y), .. ]
        """
        
        # calculate the positions
        outputPoint  = self.outputPoint()
        inputPoint   = self.inputPoint()
        
        points = []
        
        x0 = outputPoint.x()
        y0 = outputPoint.y()
        
        xN = inputPoint.x()
        yN = inputPoint.y()
        
        xC = (x0 + xN) / 2.0
        yC = (y0 + yN) / 2.0
        
        points.append((x0, y0))
        
        oloc    = self.outputLocation()
        iloc    = self.inputLocation()
        left    = XConnectionLocation.Left
        right   = XConnectionLocation.Right
        bot     = XConnectionLocation.Bottom
        top     = XConnectionLocation.Top
        
        # create a right-to-left
        if ( (oloc & right) and (iloc & left) ):
            if ( xN < (x0 + self.squashThreshold()) ):
                points.append((x0+self.padding(), y0))
                points.append((x0+self.padding(), yC))
                points.append((xN-self.padding(), yC))
                points.append((xN-self.padding(), yN))
            else:
                points.append((xC, y0))
                points.append((xC, yN))
        
        # create a left-to-right
        elif ( (oloc & left) and (iloc & right) ):
            if ( (x0 - self.squashThreshold()) < xN ):
                points.append((x0-self.padding(), y0))
                points.append((x0-self.padding(), yC))
                points.append((xN+self.padding(), yC))
                points.append((xN+self.padding(), yN))
            else:
                points.append((xC, y0))
                points.append((xC, yN))
        
        # create a bottom-to-top
        elif ( (oloc & bot) and (iloc & top) ):
            if ( yN < (y0 + self.squashThreshold()) ):
                points.append((x0, y0+self.padding()))
                points.append((xC, y0+self.padding()))
                points.append((xC, yN-self.padding()))
                points.append((xN, yN-self.padding()))
            else:
                points.append((x0, yC))
                points.append((xN, yC))
        
        # create a top-to-bottom
        elif ( (oloc & top) and (iloc & bot) ):
            if ( (y0 - self.squashThreshold()) < yN ):
                points.append((x0, y0-self.padding()))
                points.append((xC, y0-self.padding()))
                points.append((xC, yN+self.padding()))
                points.append((xN, yN+self.padding()))
            else:
                points.append((x0, yC))
                points.append((xN, yC))
        
        # create a left-to-left
        elif ( (oloc & left) and (iloc & left) ):
            xMin = min(x0-self.padding(), xN-self.padding())
            points.append((xMin, y0))
            points.append((xMin, yN))
        
        # create a right-to-right
        elif ( (oloc & right) and (iloc & right) ):
            xMax = max(x0+self.padding(), xN+self.padding())
            points.append((xMax, y0))
            points.append((xMax, yN))
        
        # create a bottom-to-bottom
        elif ( (oloc & top) and (iloc & top) ):
            yMin = min(y0-self.padding(), yN-self.padding())
            points.append((x0, yMin))
            points.append((xN, yMin))
        
        # create a bottom-to-bottom
        elif ( (oloc & bot) and (iloc & bot) ):
            yMax = max(y0+self.padding(), yN+self.padding())
            points.append((x0, yMax))
            points.append((xN, yMax))
        
        # create a bottom-to-left or left-to-bottom
        elif (  ((oloc & bot) and (iloc & left)) or 
                ((oloc & left) and (iloc & bot)) ):
            points.append((x0, yN))
        
        # create a bottom-to-right or right-to-bottom
        elif (  ((oloc & bot) and (iloc & right)) or 
                ((oloc & right) and (iloc & bot)) ):
            points.append((x0, y0))
        
        # create a top-to-left or left-to-top
        elif (  ((oloc & top) and (iloc & left)) or 
                ((oloc & left) and (iloc & top)) ):
            points.append((xN, yN))
            
        # create a top-to-right or right-to-top
        elif (  ((oloc & top) and (iloc & right)) or 
                ((oloc & right) and (iloc & top)) ):
            points.append((xN, y0))
        
        points.append((xN, yN))
        
        return points
    
    def customData( self, key, default = None ):
        """
        Returns custom defined data that can be tracked per connection.
        
        :param      key         <str>
        :param      default     <variant>
        
        :return     <variant>
        """
        return self._customData.get(str(key), default)
    
    def direction( self ):
        """
        Returns the output-to-input direction as a tuple of the output \
        and input locations.
        
        :return     (<XConnectionLocation> output, <XConnectionLocation> input)
        """
        return (self.outputLocation(), self.inputLocation())
    
    def disabledPen( self ):
        """
        Returns the pen that should be used when rendering a disabled \
        connection.
        
        :return     <QPen>
        """
        return self._disabledPen
    
    def disableWithLayer( self ):
        """
        Returns whether or not this connection's enabled state should be \
        affected by its layer.
        
        :return     <bool>
        """
        return self._disableWithLayer
    
    def disconnectSignals( self, node ):
        """
        Disconnects from signals of the inputed node, if the node is a \
        valid XNode type.
        
        :param      node    <XNode> || None
        
        :return     <bool> success
        """
        from projexui.widgets.xnodewidget.xnode import XNode
        
        # make sure we're disconnecting from a valid node
        if ( not isinstance(node, XNode) ):
            return False
        
        node.dispatch.geometryChanged.disconnect(   self.setDirty )
        node.dispatch.removed.disconnect(           self.forceRemove )
        return True
    
    def forceRemove( self ):
        """
        Removes the object from the scene by queuing it up for removal.
        """
        scene = self.scene()
        if ( scene ):
            scene.forceRemove(self)
    
    def font( self ):
        """
        Returns the font for this connection.
        
        :return     <QFont>
        """
        return self._font
    
    def hasCustomData( self, key ):
        """
        Returns whether or not there is the given key in the custom data.
        
        :param      key | <str>
        
        :return     <bool>
        """
        return str(key) in self._customData
    
    def highlightPen( self ):
        """
        Return the highlight pen for this connection.
        
        :return     <QPen>
        """
        return self._highlightPen
    
    def inputLocation( self ):
        """
        Returns the input location for this connection.
        
        :return     <XConnectionLocation>
        """
        if ( not self.autoCalculateInputLocation() ):
            return self._inputLocation
        
        # auto calculate directions based on the scene
        if ( self._outputNode ):
            outputRect = self._outputNode.sceneRect()
        else:
            y = self._outputPoint.y()
            outputRect = QRectF( self._outputPoint.x(), y, 0, 0 )
        
        if ( self._inputNode ):
            inputRect  = self._inputNode.sceneRect()
        else:
            y = self._inputPoint.y()
            inputRect  = QRectF( self._inputPoint.x(), y, 0, 0 )
        
        # use the input location as potential places where it can be
        iloc    = self._inputLocation
        left    = XConnectionLocation.Left
        right   = XConnectionLocation.Right
        top     = XConnectionLocation.Top
        bot     = XConnectionLocation.Bottom
        
        if ( self._inputNode == self._outputNode ):
            if ( iloc & right ):
                return right
            elif ( iloc & left ):
                return left
            elif ( iloc & top ):
                return top
            else:
                return bot
                
        elif ( (iloc & left)    and outputRect.right() < inputRect.left() ):
            return left
        elif ( (iloc & right)   and inputRect.right() < outputRect.left() ):
            return right
        elif ( (iloc & top)     and outputRect.bottom() < inputRect.top() ):
            return top
        elif ( (iloc & bot) ):
            return bot
        elif ( (iloc & left) ):
            return left
        elif ( (iloc & right) ):
            return right
        elif ( (iloc & top) ):
            return top
        else:
            return left
    
    def inputNode( self ):
        """
        Returns the input node that is connected to this connection.
        
        :return     <XNode>
        """
        return self._inputNode
    
    def inputFixedX( self ):
        """
        Returns the fixed X value for the input option
        
        :return     <float> || None
        """
        return self._inputFixedX
    
    def inputFixedY( self ):
        """
        Returns the fixed Y value for the input option.
        
        :return     <float> || None
        """
        return self._inputFixedY
    
    def inputPoint( self ):
        """
        Returns a scene space point that the connection \
        will draw to as its input target.  If the connection \
        has a node defined, then it will calculate the input \
        point based on the position of the node, factoring in \
        preference for input location and fixed information. \
        If there is no node connected, then the point defined \
        using the setInputPoint method will be used.
        
        :return     <QPointF>
        """
        node = self.inputNode()
        
        # return the set point
        if ( not node ):
            return self._inputPoint
        
        # otherwise, calculate the point based on location and fixed info
        ilocation   = self.inputLocation()
        ifixedx     = self.inputFixedX()
        ifixedy     = self.inputFixedY()
        return node.positionAt( ilocation, ifixedx, ifixedy )
    
    def isDirection( self, outputLocation, inputLocation ):
        """
        Checks to see if the output and input locations match the settings \
        for this item.
        
        :param      outputLocation      | <XConnectionLocation>
        :param      inputLocation       | <XConnectionLocation>
        
        :return     <bool>
        """
        return (self.isOutputLocation(outputLocation) and 
               self.isInputLocation(inputLocation))
    
    def isDirty( self ):
        """
        Returns whether or not this path object is dirty and needs to \
        be rebuilt.
        
        :return     <bool>
        """
        return self._dirty
    
    def isEnabled( self ):
        """
        Returns whether or not this connection is enabled.
        
        :sa     disableWithLayer
        
        :return     <bool>
        """
        if self._disableWithLayer and self._layer:
            lenabled = self._layer.isEnabled()
        elif self._inputNode and self._outputNode:
            lenabled = self._inputNode.isEnabled() and self._outputNode.isEnabled()
        else:
            lenabled = True
        
        return self._enabled and lenabled
    
    def isInputLocation( self, location ):
        """
        Returns whether or not the inputed location value matches the \
        given input location.
        
        :param      location    | <XConnectionLocation>
        
        :return     <bool>
        """
        return (self.inputLocation() & location) != 0
    
    def isOutputLocation( self, location ):
        """
        Returns whether or not the inputed location value matches the \
        given output location.
        
        :param      location    | <XConnectionLocation>
        
        :return     <bool>
        """
        return (self.outputLocation() & location) != 0
    
    def isStyle( self, style ):
        """
        Return whether or not the connection is set to a particular style.
        
        :param      style       | <XConnectionStyle>
        
        :return     <bool>
        """
        return (self._style & style) != 0
    
    def isVisible( self ):
        """
        Returns whether or not this connection is visible.  If either node it is
        connected to is hidden, then it should be as well.
        
        :return     <bool>
        """
        in_node  = self.inputNode()
        out_node = self.outputNode()
        
        if ( in_node and not in_node.isVisible() ):
            return False
        
        if ( out_node and not out_node.isVisible() ):
            return False
        
        return self._visible
    
    def layer( self ):
        """
        Returns the layer that this node is assigned to.
        
        :return     <XNodeLayer> || None
        """
        return self._layer
    
    def mappedPolygon( self, polygon, path = None, percent = 0.5 ):
        """
        Maps the inputed polygon to the inputed path \
        used when drawing items along the path.  If no \
        specific path is supplied, then this object's own \
        path will be used.  It will rotate and move the \
        polygon according to the inputed percentage.
        
        :param      polygon     <QPolygonF>
        :param      path        <QPainterPath>
        :param      percent     <float>
        
        :return     <QPolygonF> mapped_poly
        """
        translatePerc   = percent
        anglePerc       = percent
        
        # we don't want to allow the angle percentage greater than 0.85
        # or less than 0.05 or we won't get a good rotation angle
        if ( 0.95 <= anglePerc ):
            anglePerc = 0.98
        elif ( anglePerc <= 0.05 ):
            anglePerc = 0.05
        
        if ( not path ):
            path = self.path()
        if ( not (path and path.length()) ):
            return QPolygonF()
        
        # transform the polygon to the path
        point   = path.pointAtPercent(translatePerc)
        angle   = path.angleAtPercent(anglePerc)
        
        # rotate about the 0 axis
        transform   = QTransform().rotate(-angle)
        polygon     = transform.map(polygon)
        
        # move to the translation point
        transform   = QTransform().translate(point.x(), point.y())
        
        # create the rotated polygon
        mapped_poly = transform.map(polygon)
        self._polygons.append(mapped_poly)
        
        return mapped_poly
    
    def mousePressEvent( self, event ):
        """
        Overloads the mouse press event to handle special cases and \
        bypass when the scene is in view mode.
        
        :param      event   <QMousePressEvent>
        """
        # ignore events when the scene is in view mode
        scene = self.scene()
        if ( scene and scene.inViewMode() ):
            event.ignore()
            return
        
        # block the selection signals
        if ( scene ):
            scene.blockSelectionSignals(True)
            
            # clear the selection
            if ( not (self.isSelected() or 
                      event.modifiers() == Qt.ControlModifier) ):
                for item in scene.selectedItems():
                    if ( item != self ):
                        item.setSelected(False)
        
        # try to start the connection
        super(XNodeConnection, self).mousePressEvent(event)
        
    def mouseMoveEvent( self, event ):
        """
        Overloads the mouse move event to ignore the event when \
        the scene is in view mode.
        
        :param      event   <QMouseMoveEvent>
        """
        # ignore events when the scene is in view mode
        scene = self.scene()
        if ( scene and (scene.inViewMode() or scene.isConnecting()) ):
            event.ignore()
            return
        
        # call the base method
        super(XNodeConnection, self).mouseMoveEvent(event)
    
    def mouseReleaseEvent( self, event ):
        """
        Overloads the mouse release event to ignore the event when the \
        scene is in view mode, and release the selection block signal.
         
         :param     event   <QMouseReleaseEvent>
        """
        # ignore events when the scene is in view mode
        scene = self.scene()
        if ( scene and (scene.inViewMode() or scene.isConnecting()) ):
            event.ignore()
            return
        
        # emit the scene's connection menu requested signal if
        # the button was a right mouse button
        if ( event.button() == Qt.RightButton and scene ):
            scene.emitConnectionMenuRequested(self)
            event.accept()
        else:
            super(XNodeConnection, self).mouseReleaseEvent(event)
        
        # unblock the selection signals
        if ( scene ):
            scene.blockSelectionSignals(False)
    
    def opacity( self ):
        """
        Returns the opacity amount for this connection.
        
        :return     <float>
        """
        in_node     = self.inputNode()
        out_node    = self.outputNode()
        
        if ( in_node and out_node and \
             (in_node.isIsolateHidden() or out_node.isIsolateHidden()) ):
            return 0.1
        
        opacity = super(XNodeConnection, self).opacity()
        layer = self.layer()
        if ( layer ):
            return layer.opacity() * opacity
        
        return opacity
    
    def outputLocation( self ):
        """
        Returns the location for the output source position.
        
        :return     <XConnectionLocation>
        """
        if ( not self.autoCalculateOutputLocation() ):
            return self._outputLocation
        
        # auto calculate directions based on the scene
        if ( self._outputNode ):
            outputRect = self._outputNode.sceneRect()
        else:
            y = self._outputPoint.y()
            outputRect = QRectF( self._outputPoint.x(), y, 0, 0 )
        
        if ( self._inputNode ):
            inputRect  = self._inputNode.sceneRect()
        else:
            y = self._inputPoint.y()
            inputRect  = QRectF( self._inputPoint.x(), y, 0, 0 )
            
        oloc    = self._outputLocation
        left    = XConnectionLocation.Left
        right   = XConnectionLocation.Right
        top     = XConnectionLocation.Top
        bot     = XConnectionLocation.Bottom
        
        if ( self._inputNode == self._outputNode ):
            if ( oloc & right ):
                return right
            elif ( oloc & left ):
                return left
            elif ( oloc & top ):
                return top
            else:
                return bot
                
        elif ( (oloc & right)   and outputRect.right() < inputRect.left() ):
            return right
        elif ( (oloc & left)    and inputRect.right() < outputRect.left() ):
            return left
        elif ( (oloc & bot)     and outputRect.bottom() < inputRect.top() ):
            return bot
        elif ( (oloc & top) ):
            return top
        elif ( (oloc & right) ):
            return right
        elif ( (oloc & left) ):
            return left
        elif ( (oloc & bot) ):
            return bot
        else:
            return right
    
    def outputNode( self ):
        """
        Returns the output source node that this connection is currently \
        connected to.
        
        :return     <XNode> || None
        """
        return self._outputNode
    
    def outputFixedX( self ):
        """
        Returns the fixed X position for the output component of this \
        connection.
                    
        :return     <float> || None
        """
        return self._outputFixedX
    
    def outputFixedY( self ):
        """
        Returns the fixed Y position for the output component of this \
        connection.
                    
        :return     <float> || None
        """
        return self._outputFixedY
    
    def outputPoint( self ):
        """
        Returns a scene space point that the connection \
        will draw to as its output source.  If the connection \
        has a node defined, then it will calculate the output \
        point based on the position of the node, factoring in \
        preference for output location and fixed positions.  If \ 
        there is no node connected, then the point defined using \
        the setOutputPoint method will be used.
        
        :return     <QPointF>
        """
        node = self.outputNode()
        
        # return the set point
        if ( not node ):
            return self._outputPoint
        
        # otherwise, calculate the point based on location and fixed positions
        olocation   = self.outputLocation()
        ofixedx     = self.outputFixedX()
        ofixedy     = self.outputFixedY()
        return node.positionAt( olocation, ofixedx, ofixedy )
    
    def padding( self ):
        """
        Returns the amount of padding to be used when drawing a connection \
        that will be drawn backwards.
        
        :return     <float>
        """
        return self._padding
    
    def paint( self, painter, option, widget ):
        """
        Overloads the paint method from QGraphicsPathItem to \
        handle custom drawing of the path using this items \
        pens and polygons.
        
        :param      painter     <QPainter>
        :param      option      <QGraphicsItemStyleOption>
        :param      widget      <QWidget>
        """
        # following the arguments required by Qt
        # pylint: disable-msg=W0613
        
        painter.setOpacity(self.opacity())
        
        # show the connection selected
        if ( not self.isEnabled() ):
            pen = QPen(self.disabledPen())
        elif ( self.isSelected() ):
            pen = QPen(self.highlightPen())
        else:
            pen = QPen(self.pen())
        
        if ( self._textItem ):
            self._textItem.setOpacity(self.opacity())
            self._textItem.setDefaultTextColor(pen.color().darker(110))
        
        # rebuild first if necessary
        if ( self.isDirty() ):
            self.setPath(self.rebuild())
        
        # store the initial hint
        hint = painter.renderHints()
        painter.setRenderHint( painter.Antialiasing )
        
        pen.setWidthF(1.25)
        painter.setPen(pen)
        painter.drawPath(self.path())
        
        # redraw the polys to force-fill them
        for poly in self._polygons:
            if ( not poly.isClosed() ):
                continue
            
            painter.setBrush(pen.color())
            painter.drawPolygon(poly)
        
        # restore the render hints
        painter.setRenderHints(hint)
    
    def prepareToRemove( self ):
        """
        Handles any code that needs to run to cleanup the connection \
        before it gets removed from the scene.
        
        :return     <bool> success
        """
        # disconnect the signals from the input and output nodes
        for node in (self._outputNode, self._inputNode):
            self.disconnectSignals(node)
        
        # clear the pointers to the nodes
        self._inputNode     = None
        self._outputNode    = None
        
        return True
    
    def rebuild( self ):
        """
        Rebuilds the path for this connection based on the given connection \
        style parameters that have been set.
        
        :return     <QPainterPath>
        """
        # create the path
        path            = self.rebuildPath()
        self._polygons  = self.rebuildPolygons(path)
        
        if ( self._textItem ):
            point   = path.pointAtPercent(0.5)
            metrics = QFontMetrics(self._textItem.font())
            
            point.setY(point.y() - metrics.height() / 2.0)
            
            self._textItem.setPos(point)
        
        # create the path for the item
        for poly in self._polygons:
            path.addPolygon(poly)
        
        # unmark as dirty
        self.setDirty(False)
        
        return path
    
    def rebuildPath( self ):
        """
        Rebuilds the path for the given style options based on the currently \
        set parameters.
        
        :return     <QPainterPath>
        """
        # rebuild linear style
        if ( self.isStyle( XConnectionStyle.Linear ) ):
            return self.rebuildLinear()
        
        # rebuild block style
        elif ( self.isStyle( XConnectionStyle.Block ) ):
            return self.rebuildBlock()
        
        # rebuild smooth style
        elif ( self.isStyle( XConnectionStyle.Smooth ) ):
            return self.rebuildSmooth()
        
        # otherwise, we have an invalid style, or a style
        # defined by a subclass
        else:
            return QPainterPath()
    
    def rebuildPolygons( self, path ):
        """
        Rebuilds the polygons that will be used on this path.
        
        :param      path    | <QPainterPath>
        
        :return     <list> [ <QPolygonF>, .. ]
        """
        output = []
        
        # create the input arrow
        if ( self.showInputArrow() ):
            a = QPointF(-10, -3)
            b = QPointF(0, 0)
            c = QPointF(-10, 3)
            
            mpoly = self.mappedPolygon(QPolygonF([a, b, c, a]), path, 1.0 )
            output.append( mpoly )
        
        # create the direction arrow
        if ( self.showDirectionArrow() ):
            a = QPointF(-5, -3)
            b = QPointF(5, 0)
            c = QPointF(-5, 3)
            
            mpoly = self.mappedPolygon(QPolygonF([a, b, c, a]), path, 0.5 )
            output.append( mpoly )
        
        # create the output arrow
        if ( self.showOutputArrow() ):
            a = QPointF(10, -3)
            b = QPointF(0, 0)
            c = QPointF(10, 3)
            
            mpoly = self.mappedPolygon(QPolygonF([a, b, c, a]), path, 0 )
            output.append( mpoly )
        
        return output
        
    def rebuildLinear( self ):
        """ 
        Rebuilds a linear path from the output to input points.
        
        :return     <QPainterPath>
        """
        
        points = self.controlPoints()
        
        x0, y0 = points[0]
        xN, yN = points[-1]
        
        # create a simple line between the output and
        # input points
        path = QPainterPath()
        path.moveTo(x0, y0)
        path.lineTo(xN, yN)
        
        return path
    
    def rebuildBlock( self ):
        """
        Rebuilds a blocked path from the output to input points.
        
        :return     <QPainterPath>
        """
        # collect the control points
        points = self.controlPoints()
        
        # create the path
        path = QPainterPath()
        for i, point in enumerate(points):
            if ( not i ):
                path.moveTo( point[0], point[1] )
            else:
                path.lineTo( point[0], point[1] )
        
        return path
    
    def rebuildSmooth( self ):
        """
        Rebuilds a smooth path based on the inputed points and set \
        parameters for this item.
        
        :return     <QPainterPath>
        """
        # collect the control points
        points = self.controlPoints()
        
        # create the path
        path = QPainterPath()
        
        if ( len(points) == 3 ):
            x0, y0 = points[0]
            x1, y1 = points[1]
            xN, yN = points[2]
            
            path.moveTo(x0, y0)
            path.quadTo(x1, y1, xN, yN)
        
        elif ( len(points) == 4 ):
            x0, y0 = points[0]
            x1, y1 = points[1]
            x2, y2 = points[2]
            xN, yN = points[3]
            
            path.moveTo(x0, y0)
            path.cubicTo(x1, y1, x2, y2, xN, yN)
            
        elif ( len(points) == 6 ):
            x0, y0 = points[0]
            x1, y1 = points[1]
            x2, y2 = points[2]
            x3, y3 = points[3]
            x4, y4 = points[4]
            xN, yN = points[5]
            
            xC      = (x2+x3) / 2.0
            yC      = (y2+y3) / 2.0
            
            path.moveTo(x0, y0)
            path.cubicTo(x1, y1, x2, y2, xC, yC)
            path.cubicTo(x3, y3, x4, y4, xN, yN)
            
        else:
            x0, y0 = points[0]
            xN, yN = points[-1]
            
            path.moveTo(x0, y0)
            path.lineTo(xN, yN)
        
        return path
    
    def refreshVisible( self ):
        """
        Refreshes whether or not this node should be visible based on its
        current visible state.
        """
        super(XNodeConnection, self).setVisible(self.isVisible())
    
    def setAutoCalculateInputLocation( self, state = True ):
        """
        Sets whether or not to auto calculate the input location based on \
        the proximity to the output node or point.
        
        :param     state       | <bool>
        """
        self._autoCalculateInputLocation = state
        self.setDirty()
    
    def setAutoCalculateOutputLocation( self, state = True ):
        """
        Sets whether or not to auto calculate the input location based on \
        the proximity to the output node or point.
        
        :param     state       | <bool>
        """
        self._autoCalculateOutputLocation = state
        self.setDirty()
    
    def setCustomData( self, key, value ):
        """
        Stores the inputed value as custom data on this connection for \
        the given key.
        
        :param      key     | <str>
        :param      value   | <variant>
        """
        self._customData[str(key)] = value
    
    def setDirection( self, outputLocation, inputLocation ):
        """
        Sets the output-to-input direction by setting both the locations \
        at the same time.
        
        :param      outputLocation      | <XConnectionLocation>
        :param      inputLocation       | <XConnectionLocation>
        """
        self.setOutputLocation(outputLocation)
        self.setInputLocation(inputLocation)
    
    def setDirty( self, state = True ):
        """
        Flags the connection as being dirty and needing a rebuild.
        
        :param      state   | <bool>
        """
        self._dirty = state
        
        # set if this connection should be visible
        if ( self._inputNode and self._outputNode ):
            vis = self._inputNode.isVisible() and self._outputNode.isVisible()
            self.setVisible(vis)
    
    def setDisabledPen( self, pen ):
        """
        Sets the disabled pen that will be used when rendering a connection \
        in a disabled state.
        
        :param      pen | <QPen>
        """
        self._disabledPen = QPen(pen)
    
    def setDisableWithLayer( self, state ):
        """
        Sets whether or not this connection's layer's current state should \
        affect its enabled state.
        
        :param      state | <bool>
        """
        self._disableWithLayer = state
        self.setDirty()
    
    def setEnabled( self, state ):
        """
        Sets whether or not this connection is enabled or not.
        
        :param      state | <bool>
        """
        self._enabled = state
    
    def setFont( self, font ):
        """
        Sets the font for this connection to the inputed font.
        
        :param      font | <QFont>
        """
        self._font = font
    
    def setHighlightPen( self, pen ):
        """
        Sets the pen to be used when highlighting a selected connection.
        
        :param      pen     | <QPen> || <QColor>
        """
        self._highlightPen = QPen(pen)
    
    def setInputLocation( self, location ):
        """
        Sets the input location for where this connection should point to.
        
        :param      location       | <XConnectionLocation>
        """
        self._inputLocation = location
        self.setDirty()
    
    def setInputNode( self, node ):
        """
        Sets the node that will be recieving this connection as an input.
        
        :param      node    | <XNode>
        """
        # if the node already matches the current input node, ignore
        if ( self._inputNode == node ):
            return
        
        # disconnect from the existing node
        self.disconnectSignals( self._inputNode )
        
        # store the node
        self._inputNode = node
        
        # connect to the new node
        self.connectSignals( self._inputNode )
        
        # force the rebuilding of the path
        self.setPath(self.rebuild())
    
    def setInputFixedX( self, x ):
        """
        Sets the fixed x position for the input component of this connection.
        
        :param      x       | <float> || None
        """
        self._inputFixedX = x
        self.setDirty()
    
    def setInputFixedY( self, y ):
        """
        Sets the fixed y position for the input component of this connection.
        
        :param      y       | <float> || None
        """
        self._inputFixedY = y
        self.setDirty()
        
    def setInputPoint( self, point ):
        """
        Sets the scene level input point position to draw the connection to. \
        This is used mainly by the scene when drawing a user connection - \
        it will only be used when there is no connected input node.
        
        :param      point       | <QPointF>
        """
        self._inputPoint = point
        self.setPath(self.rebuild())
    
    def setLayer( self, layer ):
        """
        Sets the layer that this node is associated with to the given layer.
        
        :param      layer       | <XNodeLayer> || None
        
        :return     <bool> changed
        """
        if ( layer == self._layer ):
            return False
        
        self._layer = layer
        self.syncLayerData()
        
        return True
    
    def setOutputLocation( self, location ):
        """
        Sets the location for the output part of the connection to generate \
        from.
        
        :param      location      | <XConnectionLocation>
        """
        self._outputLocation = location
        self.setDirty()
    
    def setOutputNode( self, node ):
        """
        Sets the node that will be generating the output information for \
        this connection.
        
        :param      node         | <XNode>
        """
        # if the output node matches the current, ignore
        if ( node == self._outputNode ):
            return
        
        # disconnect from an existing node
        self.disconnectSignals( self._outputNode )
        
        # set the current node
        self._outputNode = node
        self.connectSignals( self._outputNode )
        
        # force the rebuilding of the path
        self.setPath( self.rebuild() )
    
    def setOutputFixedX( self, x ):
        """
        Sets the fixed x position for the output component of this connection.
        
        :param      x       | <float> || None
        """
        self._outputFixedX = x
        self.setDirty()
    
    def setOutputFixedY( self, y ):
        """
        Sets the fixed y position for the output component of this connection.
        
        :param      y       | <float> || None
        """
        self._outputFixedY = y
        self.setDirty()
        
    def setOutputPoint( self, point ):
        """
        Sets the scene space point for where this connection should draw \
        its output from.  This value will only be used if no output \
        node is defined.
        
        :param      point      | <QPointF>
        """
        self._outputPoint = point
        self.setPath( self.rebuild() )
    
    def setPadding( self, padding ):
        """
        Sets the padding amount that will be used when drawing a connection \
        whose points will overlap.
        
        :param      padding    | <float>
        """
        self._padding = padding
        self.setDirty()
    
    def setShowDirectionArrow( self, state = True ):
        """
        Marks whether or not an arrow in the center of the path should be \
        drawn, showing the direction that the connection is flowing in.
        
        :param      state      | <bool>
        """
        self._showDirectionArrow = state
        self.setDirty()
    
    def setShowInputArrow( self, state = True ):
        """
        :remarks    Marks whether or not an arrow should be shown pointing
                    at the input node.
        
        :param      state       <bool>
        """
        self._showInputArrow = state
        self.setDirty()
    
    def setShowOutputArrow( self, state = True ):
        """
        :remarks    Marks whether or not an arrow should be shown pointing at
                    the output node.
        
        :param      state       <bool>
        """
        self._showOutputArrow = state
        self.setDirty()
    
    def setSquashThreshold( self, amount ):
        """
        :remarks    Sets the threshold limit of when the connection should
                    start 'squashing', calculated based on the distance between
                    the input and output points when rebuilding.
        
        :param      amount      <float>
        """
        self._squashThreshold = amount
        self.setDirty()
    
    def setStyle( self, style ):
        """
        :remarks    Sets the style of the connection that will be used.
        
        :param      style       <XConnectionStyle>
        """
        self._style = style
        self.setDirty()
        self.update()
    
    def setVisible( self, state ):
        """
        Sets whether or not this connection's local visibility should be on.
        
        :param      state | ,bool>
        """
        self._visible = state
        
        super(XNodeConnection, self).setVisible(self.isVisible())
    
    def setZValue( self, value ):
        """
        Sets the z value for this connection, also updating the text item to 
        match the value if one is defined.
        
        :param      value | <int>
        """
        super(XNodeConnection, self).setZValue(value)
        
        if ( self._textItem ):
            self._textItem.setZValue(value)
    
    def showDirectionArrow( self ):
        """
        :remarks    Return whether or not the direction arrow is visible
                    for this connection.
        
        :return     <bool>
        """
        return self._showDirectionArrow
    
    def showInputArrow( self ):
        """
        :remarks    Return whether or not the input arrow is visible
                    for this connection.
        
        :return     <bool>
        """
        return self._showInputArrow
    
    def showOutputArrow( self ):
        """
        :remarks    Return whether or not the output arrow is visible
                    for this connection.
        
        :return     <bool>
        """
        return self._showOutputArrow
    
    def squashThreshold( self ):
        """
        :remarks    Returns the sqash threshold for when the line
                    should be squashed based on the input and output
                    points becoming too close together.
        
        :return     <float>
        """
        return self._squashThreshold
    
    def setText( self, text ):
        """
        Sets the text for this connection to the inputed text.
        
        :param      text | <str>
        """
        self._text = text
        
        if ( text ):
            if ( self._textItem is None ):
                self._textItem = QGraphicsTextItem()
                self._textItem.setParentItem(self)
            
            self._textItem.setPlainText(text)
            
        elif ( self._textItem ):
            self.scene().removeItem(self._textItem)
            self._textItem = None
    
    def style( self ):
        """
        :remarks    Returns the style of the connection that is being drawn.
        
        :return     style       <XConnectionStyle>
        """
        return self._style
    
    def syncLayerData( self, layerData = None ):
        """
        Syncs the layer information for this item from the given layer data.
        
        :param      layerData | <dict>
        """
        if ( not self._layer ):
            return
        
        if ( not layerData ):
            layerData = self._layer.layerData()
        
        self.setVisible( layerData.get('visible', True) )
        
        if ( layerData.get('current') ):
            # set the default parameters
            self.setFlags( self.ItemIsSelectable )
            self.setAcceptHoverEvents(True)
            self.setZValue(99)
            
        else:
            # set the default parameters
            self.setFlags(  self.GraphicsItemFlags(0) )
            self.setAcceptHoverEvents(True)
            self.setZValue(layerData.get('zValue', 0)-1)
    
    def text( self ):
        """
        Returns the text for this connection.
        
        :return     <str>
        """
        return self._text
示例#22
0
class NodeItem(QGraphicsObject):
    """
    An widget node item in the canvas.
    """

    #: Signal emitted when the scene position of the node has changed.
    positionChanged = Signal()

    #: Signal emitted when the geometry of the channel anchors changes.
    anchorGeometryChanged = Signal()

    #: Signal emitted when the item has been activated (by a mouse double
    #: click or a keyboard)
    activated = Signal()

    #: The item is under the mouse.
    hovered = Signal()

    #: Span of the anchor in degrees
    ANCHOR_SPAN_ANGLE = 90

    #: Z value of the item
    Z_VALUE = 100

    def __init__(self, widget_description=None, parent=None, **kwargs):
        QGraphicsObject.__init__(self, parent, **kwargs)
        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
        self.setFlag(QGraphicsItem.ItemHasNoContents, True)
        self.setFlag(QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.setFlag(QGraphicsItem.ItemIsFocusable, True)

        # central body shape item
        self.shapeItem = None

        # in/output anchor items
        self.inputAnchorItem = None
        self.outputAnchorItem = None

        # title text item
        self.captionTextItem = None

        # error, warning, info items
        self.errorItem = None
        self.warningItem = None
        self.infoItem = None

        self.__title = ""
        self.__processingState = 0
        self.__progress = -1

        self.__error = None
        self.__warning = None
        self.__info = None

        self.__anchorLayout = None
        self.__animationEnabled = False

        self.setZValue(self.Z_VALUE)
        self.setupGraphics()

        self.setWidgetDescription(widget_description)

    @classmethod
    def from_node(cls, node):
        """
        Create an :class:`NodeItem` instance and initialize it from a
        :class:`SchemeNode` instance.

        """
        self = cls()
        self.setWidgetDescription(node.description)
        #        self.setCategoryDescription(node.category)
        return self

    @classmethod
    def from_node_meta(cls, meta_description):
        """
        Create an `NodeItem` instance from a node meta description.
        """
        self = cls()
        self.setWidgetDescription(meta_description)
        return self

    def setupGraphics(self):
        """
        Set up the graphics.
        """
        shape_rect = QRectF(-24, -24, 48, 48)

        self.shapeItem = NodeBodyItem(self)
        self.shapeItem.setShapeRect(shape_rect)
        self.shapeItem.setAnimationEnabled(self.__animationEnabled)

        # Rect for widget's 'ears'.
        anchor_rect = QRectF(-31, -31, 62, 62)
        self.inputAnchorItem = SinkAnchorItem(self)
        input_path = QPainterPath()
        start_angle = 180 - self.ANCHOR_SPAN_ANGLE / 2
        input_path.arcMoveTo(anchor_rect, start_angle)
        input_path.arcTo(anchor_rect, start_angle, self.ANCHOR_SPAN_ANGLE)
        self.inputAnchorItem.setAnchorPath(input_path)

        self.outputAnchorItem = SourceAnchorItem(self)
        output_path = QPainterPath()
        start_angle = self.ANCHOR_SPAN_ANGLE / 2
        output_path.arcMoveTo(anchor_rect, start_angle)
        output_path.arcTo(anchor_rect, start_angle, -self.ANCHOR_SPAN_ANGLE)
        self.outputAnchorItem.setAnchorPath(output_path)

        self.inputAnchorItem.hide()
        self.outputAnchorItem.hide()

        # Title caption item
        self.captionTextItem = QGraphicsTextItem(self)
        self.captionTextItem.setPlainText("")
        self.captionTextItem.setPos(0, 33)

        def iconItem(standard_pixmap):
            item = GraphicsIconItem(self,
                                    icon=standard_icon(standard_pixmap),
                                    iconSize=QSize(16, 16))
            item.hide()
            return item

        self.errorItem = iconItem(QStyle.SP_MessageBoxCritical)
        self.warningItem = iconItem(QStyle.SP_MessageBoxWarning)
        self.infoItem = iconItem(QStyle.SP_MessageBoxInformation)

    # TODO: Remove the set[Widget|Category]Description. The user should
    # handle setting of icons, title, ...
    def setWidgetDescription(self, desc):
        """
        Set widget description.
        """
        self.widget_description = desc
        if desc is None:
            return

        icon = icon_loader.from_description(desc).get(desc.icon)
        if icon:
            self.setIcon(icon)

        if not self.title():
            self.setTitle(desc.name)

        if desc.inputs:
            self.inputAnchorItem.show()
        if desc.outputs:
            self.outputAnchorItem.show()

        tooltip = NodeItem_toolTipHelper(self)
        self.setToolTip(tooltip)

    def setWidgetCategory(self, desc):
        """
        Set the widget category.
        """
        self.category_description = desc
        if desc and desc.background:
            background = NAMED_COLORS.get(desc.background, desc.background)
            color = QColor(background)
            if color.isValid():
                self.setColor(color)

    def setIcon(self, icon):
        """
        Set the node item's icon (:class:`QIcon`).
        """
        if isinstance(icon, QIcon):
            self.icon_item = GraphicsIconItem(self.shapeItem,
                                              icon=icon,
                                              iconSize=QSize(36, 36))
            self.icon_item.setPos(-18, -18)
        else:
            raise TypeError

    def setColor(self, color, selectedColor=None):
        """
        Set the widget color.
        """
        if selectedColor is None:
            selectedColor = saturated(color, 150)
        palette = create_palette(color, selectedColor)
        self.shapeItem.setPalette(palette)

    def setPalette(self, palette):
        # TODO: The palette should override the `setColor`
        raise NotImplementedError

    def setTitle(self, title):
        """
        Set the node title. The title text is displayed at the bottom of the
        node.

        """
        self.__title = title
        self.__updateTitleText()

    def title(self):
        """
        Return the node title.
        """
        return self.__title

    title_ = Property(unicode,
                      fget=title,
                      fset=setTitle,
                      doc="Node title text.")

    def setFont(self, font):
        """
        Set the title text font (:class:`QFont`).
        """
        if font != self.font():
            self.prepareGeometryChange()
            self.captionTextItem.setFont(font)
            self.__updateTitleText()

    def font(self):
        """
        Return the title text font.
        """
        return self.captionTextItem.font()

    def setAnimationEnabled(self, enabled):
        """
        Set the node animation enabled state.
        """
        if self.__animationEnabled != enabled:
            self.__animationEnabled = enabled
            self.shapeItem.setAnimationEnabled(enabled)

    def animationEnabled(self):
        """
        Are node animations enabled.
        """
        return self.__animationEnabled

    def setProcessingState(self, state):
        """
        Set the node processing state i.e. the node is processing
        (is busy) or is idle.

        """
        if self.__processingState != state:
            self.__processingState = state
            self.shapeItem.setProcessingState(state)
            if not state:
                # Clear the progress meter.
                self.setProgress(-1)
                if self.__animationEnabled:
                    self.shapeItem.ping()

    def processingState(self):
        """
        The node processing state.
        """
        return self.__processingState

    processingState_ = Property(int,
                                fget=processingState,
                                fset=setProcessingState)

    def setProgress(self, progress):
        """
        Set the node work progress state (number between 0 and 100).
        """
        if progress is None or progress < 0:
            progress = -1

        progress = max(min(progress, 100), -1)
        if self.__progress != progress:
            self.__progress = progress
            self.shapeItem.setProgress(progress)
            self.__updateTitleText()

    def progress(self):
        """
        Return the node work progress state.
        """
        return self.__progress

    progress_ = Property(float,
                         fget=progress,
                         fset=setProgress,
                         doc="Node progress state.")

    def setProgressMessage(self, message):
        """
        Set the node work progress message.

        .. note:: Not yet implemented

        """
        pass

    def setErrorMessage(self, message):
        if self.__error != message:
            self.__error = message
            self.__updateMessages()

    def setWarningMessage(self, message):
        if self.__warning != message:
            self.__warning = message
            self.__updateMessages()

    def setInfoMessage(self, message):
        if self.__info != message:
            self.__info = message
            self.__updateMessages()

    def newInputAnchor(self):
        """
        Create and return a new input :class:`AnchorPoint`.
        """
        if not (self.widget_description and self.widget_description.inputs):
            raise ValueError("Widget has no inputs.")

        anchor = AnchorPoint()
        self.inputAnchorItem.addAnchor(anchor, position=1.0)

        positions = self.inputAnchorItem.anchorPositions()
        positions = uniform_linear_layout(positions)
        self.inputAnchorItem.setAnchorPositions(positions)

        return anchor

    def removeInputAnchor(self, anchor):
        """
        Remove input anchor.
        """
        self.inputAnchorItem.removeAnchor(anchor)

        positions = self.inputAnchorItem.anchorPositions()
        positions = uniform_linear_layout(positions)
        self.inputAnchorItem.setAnchorPositions(positions)

    def newOutputAnchor(self):
        """
        Create and return a new output :class:`AnchorPoint`.
        """
        if not (self.widget_description and self.widget_description.outputs):
            raise ValueError("Widget has no outputs.")

        anchor = AnchorPoint(self)
        self.outputAnchorItem.addAnchor(anchor, position=1.0)

        positions = self.outputAnchorItem.anchorPositions()
        positions = uniform_linear_layout(positions)
        self.outputAnchorItem.setAnchorPositions(positions)

        return anchor

    def removeOutputAnchor(self, anchor):
        """
        Remove output anchor.
        """
        self.outputAnchorItem.removeAnchor(anchor)

        positions = self.outputAnchorItem.anchorPositions()
        positions = uniform_linear_layout(positions)
        self.outputAnchorItem.setAnchorPositions(positions)

    def inputAnchors(self):
        """
        Return a list of all input anchor points.
        """
        return self.inputAnchorItem.anchorPoints()

    def outputAnchors(self):
        """
        Return a list of all output anchor points.
        """
        return self.outputAnchorItem.anchorPoints()

    def setAnchorRotation(self, angle):
        """
        Set the anchor rotation.
        """
        self.inputAnchorItem.setRotation(angle)
        self.outputAnchorItem.setRotation(angle)
        self.anchorGeometryChanged.emit()

    def anchorRotation(self):
        """
        Return the anchor rotation.
        """
        return self.inputAnchorItem.rotation()

    def boundingRect(self):
        # TODO: Important because of this any time the child
        # items change geometry the self.prepareGeometryChange()
        # needs to be called.
        return self.childrenBoundingRect()

    def shape(self):
        # Shape for mouse hit detection.
        # TODO: Should this return the union of all child items?
        return self.shapeItem.shape()

    def __updateTitleText(self):
        """
        Update the title text item.
        """
        title_safe = escape(self.title())
        if self.progress() > 0:
            text = '<div align="center">%s<br/>%i%%</div>' % \
                   (title_safe, int(self.progress()))
        else:
            text = '<div align="center">%s</div>' % \
                   (title_safe)

        # The NodeItems boundingRect could change.
        self.prepareGeometryChange()
        self.captionTextItem.setHtml(text)
        self.captionTextItem.document().adjustSize()
        width = self.captionTextItem.textWidth()
        self.captionTextItem.setPos(-width / 2.0, 33)

    def __updateMessages(self):
        """
        Update message items (position, visibility and tool tips).
        """
        items = [self.errorItem, self.warningItem, self.infoItem]
        messages = [self.__error, self.__warning, self.__info]
        for message, item in zip(messages, items):
            item.setVisible(bool(message))
            item.setToolTip(message or "")
        shown = [item for item in items if item.isVisible()]
        count = len(shown)
        if count:
            spacing = 3
            rects = [item.boundingRect() for item in shown]
            width = sum(rect.width() for rect in rects)
            width += spacing * max(0, count - 1)
            height = max(rect.height() for rect in rects)
            origin = self.shapeItem.boundingRect().top() - spacing - height
            origin = QPointF(-width / 2, origin)
            for item, rect in zip(shown, rects):
                item.setPos(origin)
                origin = origin + QPointF(rect.width() + spacing, 0)

    def mousePressEvent(self, event):
        if self.shapeItem.path().contains(event.pos()):
            return QGraphicsObject.mousePressEvent(self, event)
        else:
            event.ignore()

    def mouseDoubleClickEvent(self, event):
        if self.shapeItem.path().contains(event.pos()):
            QGraphicsObject.mouseDoubleClickEvent(self, event)
            QTimer.singleShot(0, self.activated.emit)
        else:
            event.ignore()

    def contextMenuEvent(self, event):
        if self.shapeItem.path().contains(event.pos()):
            return QGraphicsObject.contextMenuEvent(self, event)
        else:
            event.ignore()

    def focusInEvent(self, event):
        self.shapeItem.setHasFocus(True)
        return QGraphicsObject.focusInEvent(self, event)

    def focusOutEvent(self, event):
        self.shapeItem.setHasFocus(False)
        return QGraphicsObject.focusOutEvent(self, event)

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemSelectedChange:
            self.shapeItem.setSelected(value.toBool())
        elif change == QGraphicsItem.ItemPositionHasChanged:
            self.positionChanged.emit()

        return QGraphicsObject.itemChange(self, change, value)