예제 #1
0
파일: role.py 프로젝트: gitter-badger/eddy
class RoleNode(AbstractResizableNode):
    """
    This class implements the 'Role' node.
    """
    indexL = 0
    indexB = 1
    indexT = 3
    indexR = 2
    indexE = 4

    identities = {Identity.Role}
    item = Item.RoleNode
    minheight = 50
    minwidth = 70

    def __init__(self, width=minwidth, height=minheight, brush=None, **kwargs):
        """
        Initialize the node.
        :type width: int
        :type height: int
        :type brush: QBrush
        """
        super().__init__(**kwargs)
        w = max(width, self.minwidth)
        h = max(height, self.minheight)
        s = self.handleSize
        self.brush = brush or QBrush(QColor(252, 252, 252))
        self.pen = QPen(QColor(0, 0, 0), 1.1, Qt.SolidLine)
        self.polygon = self.createPolygon(w, h)
        self.background = self.createBackground(w + s, h + s)
        self.selection = self.createSelection(w + s, h + s)
        self.label = Label('role', parent=self)
        self.label.updatePos()
        self.updateHandles()

    ####################################################################################################################
    #                                                                                                                  #
    #   PROPERTIES                                                                                                     #
    #                                                                                                                  #
    ####################################################################################################################

    @property
    def asymmetric(self):
        """
        Tells whether the Role is defined as asymmetric.
        :rtype: bool
        """
        scene = self.scene()
        meta = scene.meta.metaFor(self.item, self.text())
        return meta.asymmetry

    @asymmetric.setter
    def asymmetric(self, value):
        """
        Set the Role asymmetry property.
        :type value: bool
        """
        scene = self.scene()
        meta = scene.meta.metaFor(self.item, self.text())
        meta.asymmetry = bool(value)
        scene.meta.add(self.item, self.text(), meta)

    @property
    def functional(self):
        """
        Tells whether the Role is defined as functional.
        :rtype: bool
        """
        scene = self.scene()
        meta = scene.meta.metaFor(self.item, self.text())
        return meta.functionality

    @functional.setter
    def functional(self, value):
        """
        Set the Role functionality property.
        :type value: bool
        """
        scene = self.scene()
        meta = scene.meta.metaFor(self.item, self.text())
        meta.functionality = bool(value)
        scene.meta.add(self.item, self.text(), meta)

    @property
    def identity(self):
        """
        Returns the identity of the current node.
        :rtype: Identity
        """
        return Identity.Role

    @identity.setter
    def identity(self, identity):
        """
        Set the identity of the current node.
        :type identity: Identity
        """
        pass

    @property
    def inverseFunctional(self):
        """
        Tells whether the Role is defined as inverse functional.
        :rtype: bool
        """
        scene = self.scene()
        meta = scene.meta.metaFor(self.item, self.text())
        return meta.inverseFunctionality

    @inverseFunctional.setter
    def inverseFunctional(self, value):
        """
        Set the Role inverse functionality property.
        :type value: bool
        """
        scene = self.scene()
        meta = scene.meta.metaFor(self.item, self.text())
        meta.inverseFunctionality = bool(value)
        scene.meta.add(self.item, self.text(), meta)

    @property
    def irreflexive(self):
        """
        Tells whether the Role is defined as irreflexive.
        :rtype: bool
        """
        scene = self.scene()
        meta = scene.meta.metaFor(self.item, self.text())
        return meta.irreflexivity

    @irreflexive.setter
    def irreflexive(self, value):
        """
        Set the Role irreflexivity property.
        :type value: bool
        """
        scene = self.scene()
        meta = scene.meta.metaFor(self.item, self.text())
        meta.irreflexivity = bool(value)
        scene.meta.add(self.item, self.text(), meta)

    @property
    def reflexive(self):
        """
        Tells whether the Role is defined as reflexive.
        :rtype: bool
        """
        scene = self.scene()
        meta = scene.meta.metaFor(self.item, self.text())
        return meta.reflexivity

    @reflexive.setter
    def reflexive(self, value):
        """
        Set the Role reflexivity property.
        :type value: bool
        """
        scene = self.scene()
        meta = scene.meta.metaFor(self.item, self.text())
        meta.reflexivity = bool(value)
        scene.meta.add(self.item, self.text(), meta)

    @property
    def special(self):
        """
        Returns the special type of this node.
        :rtype: Special
        """
        return Special.forValue(self.text())

    @property
    def symmetric(self):
        """
        Tells whether the Role is defined as symmetric.
        :rtype: bool
        """
        scene = self.scene()
        meta = scene.meta.metaFor(self.item, self.text())
        return meta.symmetry

    @symmetric.setter
    def symmetric(self, value):
        """
        Set the Role symmetry property.
        :type value: bool
        """
        scene = self.scene()
        meta = scene.meta.metaFor(self.item, self.text())
        meta.symmetry = bool(value)
        scene.meta.add(self.item, self.text(), meta)

    @property
    def transitive(self):
        """
        Tells whether the Role is defined as transitive.
        :rtype: bool
        """
        scene = self.scene()
        meta = scene.meta.metaFor(self.item, self.text())
        return meta.transitivity

    @transitive.setter
    def transitive(self, value):
        """
        Set the Role transitivity property.
        :type value: bool
        """
        scene = self.scene()
        meta = scene.meta.metaFor(self.item, self.text())
        meta.transitivity = bool(value)
        scene.meta.add(self.item, self.text(), meta)

    ####################################################################################################################
    #                                                                                                                  #
    #   INTERFACE                                                                                                      #
    #                                                                                                                  #
    ####################################################################################################################

    def copy(self, scene):
        """
        Create a copy of the current item.
        :type scene: DiagramScene
        """
        kwargs = {
            'id': self.id,
            'brush': self.brush,
            'height': self.height(),
            'width': self.width(),
        }
        node = scene.factory.create(item=self.item, scene=scene, **kwargs)
        node.setPos(self.pos())
        node.setText(self.text())
        node.setTextPos(node.mapFromScene(self.mapToScene(self.textPos())))
        return node

    @staticmethod
    def createBackground(width, height):
        """
        Returns the initialized background polygon according to the given width/height.
        :type width: int
        :type height: int
        :rtype: QPolygonF
        """
        return QPolygonF([
            QPointF(-width / 2, 0),
            QPointF(0, +height / 2),
            QPointF(+width / 2, 0),
            QPointF(0, -height / 2),
            QPointF(-width / 2, 0)
        ])

    @staticmethod
    def createPolygon(width, height):
        """
        Returns the initialized polygon according to the given width/height.
        :type width: int
        :type height: int
        :rtype: QPolygonF
        """
        return QPolygonF([
            QPointF(-width / 2, 0),
            QPointF(0, +height / 2),
            QPointF(+width / 2, 0),
            QPointF(0, -height / 2),
            QPointF(-width / 2, 0)
        ])

    def height(self):
        """
        Returns the height of the shape.
        :rtype: int
        """
        return self.polygon[self.indexB].y() - self.polygon[self.indexT].y()

    def interactiveResize(self, mousePos):
        """
        Handle the interactive resize of the shape.
        :type mousePos: QPointF
        """
        scene = self.scene()
        snap = scene.mainwindow.snapToGrid
        size = scene.GridSize
        offset = self.handleSize + self.handleMove
        moved = self.label.moved

        R = QRectF(self.boundingRect())
        D = QPointF(0, 0)

        minBoundW = self.minwidth + offset * 2
        minBoundH = self.minheight + offset * 2

        self.prepareGeometryChange()

        if self.mousePressHandle == self.handleTL:

            fromX = self.mousePressBound.left()
            fromY = self.mousePressBound.top()
            toX = fromX + mousePos.x() - self.mousePressPos.x()
            toY = fromY + mousePos.y() - self.mousePressPos.y()
            toX = snapF(toX, size, -offset, snap)
            toY = snapF(toY, size, -offset, snap)
            D.setX(toX - fromX)
            D.setY(toY - fromY)
            R.setLeft(toX)
            R.setTop(toY)

            ## CLAMP SIZE
            if R.width() < minBoundW:
                D.setX(D.x() - minBoundW + R.width())
                R.setLeft(R.left() - minBoundW + R.width())
            if R.height() < minBoundH:
                D.setY(D.y() - minBoundH + R.height())
                R.setTop(R.top() - minBoundH + R.height())

            self.selection.setLeft(R.left())
            self.selection.setTop(R.top())

            self.background[self.indexT] = QPointF(R.left() + R.width() / 2,
                                                   R.top())
            self.background[self.indexB] = QPointF(
                R.left() + R.width() / 2, self.background[self.indexB].y())
            self.background[self.indexL] = QPointF(R.left(),
                                                   R.top() + R.height() / 2)
            self.background[self.indexE] = QPointF(R.left(),
                                                   R.top() + R.height() / 2)
            self.background[self.indexR] = QPointF(
                self.background[self.indexR].x(),
                R.top() + R.height() / 2)

            self.polygon[self.indexT] = QPointF(R.left() + R.width() / 2,
                                                R.top() + offset)
            self.polygon[self.indexB] = QPointF(R.left() + R.width() / 2,
                                                self.polygon[self.indexB].y())
            self.polygon[self.indexL] = QPointF(R.left() + offset,
                                                R.top() + R.height() / 2)
            self.polygon[self.indexE] = QPointF(R.left() + offset,
                                                R.top() + R.height() / 2)
            self.polygon[self.indexR] = QPointF(self.polygon[self.indexR].x(),
                                                R.top() + R.height() / 2)

        elif self.mousePressHandle == self.handleTM:

            fromY = self.mousePressBound.top()
            toY = fromY + mousePos.y() - self.mousePressPos.y()
            toY = snapF(toY, size, -offset, snap)
            D.setY(toY - fromY)
            R.setTop(toY)

            ## CLAMP SIZE
            if R.height() < minBoundH:
                D.setY(D.y() - minBoundH + R.height())
                R.setTop(R.top() - minBoundH + R.height())

            self.selection.setTop(R.top())

            self.background[self.indexT] = QPointF(
                self.background[self.indexT].x(), R.top())
            self.background[self.indexL] = QPointF(
                self.background[self.indexL].x(),
                R.top() + R.height() / 2)
            self.background[self.indexE] = QPointF(
                self.background[self.indexE].x(),
                R.top() + R.height() / 2)
            self.background[self.indexR] = QPointF(
                self.background[self.indexR].x(),
                R.top() + R.height() / 2)

            self.polygon[self.indexT] = QPointF(self.polygon[self.indexT].x(),
                                                R.top() + offset)
            self.polygon[self.indexL] = QPointF(self.polygon[self.indexL].x(),
                                                R.top() + R.height() / 2)
            self.polygon[self.indexE] = QPointF(self.polygon[self.indexE].x(),
                                                R.top() + R.height() / 2)
            self.polygon[self.indexR] = QPointF(self.polygon[self.indexR].x(),
                                                R.top() + R.height() / 2)

        elif self.mousePressHandle == self.handleTR:

            fromX = self.mousePressBound.right()
            fromY = self.mousePressBound.top()
            toX = fromX + mousePos.x() - self.mousePressPos.x()
            toY = fromY + mousePos.y() - self.mousePressPos.y()
            toX = snapF(toX, size, +offset, snap)
            toY = snapF(toY, size, -offset, snap)
            D.setX(toX - fromX)
            D.setY(toY - fromY)
            R.setRight(toX)
            R.setTop(toY)

            ## CLAMP SIZE
            if R.width() < minBoundW:
                D.setX(D.x() + minBoundW - R.width())
                R.setRight(R.right() + minBoundW - R.width())
            if R.height() < minBoundH:
                D.setY(D.y() - minBoundH + R.height())
                R.setTop(R.top() - minBoundH + R.height())

            self.selection.setRight(R.right())
            self.selection.setTop(R.top())

            self.background[self.indexT] = QPointF(R.right() - R.width() / 2,
                                                   R.top())
            self.background[self.indexB] = QPointF(
                R.right() - R.width() / 2, self.background[self.indexB].y())
            self.background[self.indexL] = QPointF(
                self.background[self.indexL].x(),
                R.top() + R.height() / 2)
            self.background[self.indexE] = QPointF(
                self.background[self.indexE].x(),
                R.top() + R.height() / 2)
            self.background[self.indexR] = QPointF(R.right(),
                                                   R.top() + R.height() / 2)

            self.polygon[self.indexT] = QPointF(R.right() - R.width() / 2,
                                                R.top() + offset)
            self.polygon[self.indexB] = QPointF(R.right() - R.width() / 2,
                                                self.polygon[self.indexB].y())
            self.polygon[self.indexL] = QPointF(self.polygon[self.indexL].x(),
                                                R.top() + R.height() / 2)
            self.polygon[self.indexE] = QPointF(self.polygon[self.indexE].x(),
                                                R.top() + R.height() / 2)
            self.polygon[self.indexR] = QPointF(R.right() - offset,
                                                R.top() + R.height() / 2)

        elif self.mousePressHandle == self.handleML:

            fromX = self.mousePressBound.left()
            toX = fromX + mousePos.x() - self.mousePressPos.x()
            toX = snapF(toX, size, -offset, snap)
            D.setX(toX - fromX)
            R.setLeft(toX)

            ## CLAMP SIZE
            if R.width() < minBoundW:
                D.setX(D.x() - minBoundW + R.width())
                R.setLeft(R.left() - minBoundW + R.width())

            self.selection.setLeft(R.left())

            self.background[self.indexL] = QPointF(
                R.left(),
                self.mousePressBound.top() + self.mousePressBound.height() / 2)
            self.background[self.indexE] = QPointF(
                R.left(),
                self.mousePressBound.top() + self.mousePressBound.height() / 2)
            self.background[self.indexT] = QPointF(
                R.left() + R.width() / 2, self.background[self.indexT].y())
            self.background[self.indexB] = QPointF(
                R.left() + R.width() / 2, self.background[self.indexB].y())

            self.polygon[self.indexL] = QPointF(
                R.left() + offset,
                self.mousePressBound.top() + self.mousePressBound.height() / 2)
            self.polygon[self.indexE] = QPointF(
                R.left() + offset,
                self.mousePressBound.top() + self.mousePressBound.height() / 2)
            self.polygon[self.indexT] = QPointF(R.left() + R.width() / 2,
                                                self.polygon[self.indexT].y())
            self.polygon[self.indexB] = QPointF(R.left() + R.width() / 2,
                                                self.polygon[self.indexB].y())

        elif self.mousePressHandle == self.handleMR:

            fromX = self.mousePressBound.right()
            toX = fromX + mousePos.x() - self.mousePressPos.x()
            toX = snapF(toX, size, +offset, snap)
            D.setX(toX - fromX)
            R.setRight(toX)

            ## CLAMP SIZE
            if R.width() < minBoundW:
                D.setX(D.x() + minBoundW - R.width())
                R.setRight(R.right() + minBoundW - R.width())

            self.selection.setRight(R.right())

            self.background[self.indexR] = QPointF(
                R.right(),
                self.mousePressBound.top() + self.mousePressBound.height() / 2)
            self.background[self.indexT] = QPointF(
                R.right() - R.width() / 2, self.background[self.indexT].y())
            self.background[self.indexB] = QPointF(
                R.right() - R.width() / 2, self.background[self.indexB].y())

            self.polygon[self.indexR] = QPointF(
                R.right() - offset,
                self.mousePressBound.top() + self.mousePressBound.height() / 2)
            self.polygon[self.indexT] = QPointF(R.right() - R.width() / 2,
                                                self.polygon[self.indexT].y())
            self.polygon[self.indexB] = QPointF(R.right() - R.width() / 2,
                                                self.polygon[self.indexB].y())

        elif self.mousePressHandle == self.handleBL:

            fromX = self.mousePressBound.left()
            fromY = self.mousePressBound.bottom()
            toX = fromX + mousePos.x() - self.mousePressPos.x()
            toY = fromY + mousePos.y() - self.mousePressPos.y()
            toX = snapF(toX, size, -offset, snap)
            toY = snapF(toY, size, +offset, snap)
            D.setX(toX - fromX)
            D.setY(toY - fromY)
            R.setLeft(toX)
            R.setBottom(toY)

            ## CLAMP SIZE
            if R.width() < minBoundW:
                D.setX(D.x() - minBoundW + R.width())
                R.setLeft(R.left() - minBoundW + R.width())
            if R.height() < minBoundH:
                D.setY(D.y() + minBoundH - R.height())
                R.setBottom(R.bottom() + minBoundH - R.height())

            self.selection.setLeft(R.left())
            self.selection.setBottom(R.bottom())

            self.background[self.indexT] = QPointF(
                R.left() + R.width() / 2, self.background[self.indexT].y())
            self.background[self.indexB] = QPointF(R.left() + R.width() / 2,
                                                   R.bottom())
            self.background[self.indexL] = QPointF(R.left(),
                                                   R.bottom() - R.height() / 2)
            self.background[self.indexE] = QPointF(R.left(),
                                                   R.bottom() - R.height() / 2)
            self.background[self.indexR] = QPointF(
                self.background[self.indexR].x(),
                R.bottom() - R.height() / 2)

            self.polygon[self.indexT] = QPointF(R.left() + R.width() / 2,
                                                self.polygon[self.indexT].y())
            self.polygon[self.indexB] = QPointF(R.left() + R.width() / 2,
                                                R.bottom() - offset)
            self.polygon[self.indexL] = QPointF(R.left() + offset,
                                                R.bottom() - R.height() / 2)
            self.polygon[self.indexE] = QPointF(R.left() + offset,
                                                R.bottom() - R.height() / 2)
            self.polygon[self.indexR] = QPointF(self.polygon[self.indexR].x(),
                                                R.bottom() - R.height() / 2)

        elif self.mousePressHandle == self.handleBM:

            fromY = self.mousePressBound.bottom()
            toY = fromY + mousePos.y() - self.mousePressPos.y()
            toY = snapF(toY, size, +offset, snap)
            D.setY(toY - fromY)
            R.setBottom(toY)

            ## CLAMP SIZE
            if R.height() < minBoundH:
                D.setY(D.y() + minBoundH - R.height())
                R.setBottom(R.bottom() + minBoundH - R.height())

            self.selection.setBottom(R.bottom())

            self.background[self.indexB] = QPointF(
                self.background[self.indexB].x(), R.bottom())
            self.background[self.indexL] = QPointF(
                self.background[self.indexL].x(),
                R.top() + R.height() / 2)
            self.background[self.indexE] = QPointF(
                self.background[self.indexE].x(),
                R.top() + R.height() / 2)
            self.background[self.indexR] = QPointF(
                self.background[self.indexR].x(),
                R.top() + R.height() / 2)

            self.polygon[self.indexB] = QPointF(self.polygon[self.indexB].x(),
                                                R.bottom() - offset)
            self.polygon[self.indexL] = QPointF(self.polygon[self.indexL].x(),
                                                R.top() + R.height() / 2)
            self.polygon[self.indexE] = QPointF(self.polygon[self.indexE].x(),
                                                R.top() + R.height() / 2)
            self.polygon[self.indexR] = QPointF(self.polygon[self.indexR].x(),
                                                R.top() + R.height() / 2)

        elif self.mousePressHandle == self.handleBR:

            fromX = self.mousePressBound.right()
            fromY = self.mousePressBound.bottom()
            toX = fromX + mousePos.x() - self.mousePressPos.x()
            toY = fromY + mousePos.y() - self.mousePressPos.y()
            toX = snapF(toX, size, +offset, snap)
            toY = snapF(toY, size, +offset, snap)
            D.setX(toX - fromX)
            D.setY(toY - fromY)
            R.setRight(toX)
            R.setBottom(toY)

            ## CLAMP SIZE
            if R.width() < minBoundW:
                D.setX(D.x() + minBoundW - R.width())
                R.setRight(R.right() + minBoundW - R.width())
            if R.height() < minBoundH:
                D.setY(D.y() + minBoundH - R.height())
                R.setBottom(R.bottom() + minBoundH - R.height())

            self.selection.setRight(R.right())
            self.selection.setBottom(R.bottom())

            self.background[self.indexT] = QPointF(
                R.right() - R.width() / 2, self.background[self.indexT].y())
            self.background[self.indexB] = QPointF(R.right() - R.width() / 2,
                                                   R.bottom())
            self.background[self.indexL] = QPointF(
                self.background[self.indexL].x(),
                R.bottom() - R.height() / 2)
            self.background[self.indexE] = QPointF(
                self.background[self.indexE].x(),
                R.bottom() - R.height() / 2)
            self.background[self.indexR] = QPointF(R.right(),
                                                   R.bottom() - R.height() / 2)

            self.polygon[self.indexT] = QPointF(R.right() - R.width() / 2,
                                                self.polygon[self.indexT].y())
            self.polygon[self.indexB] = QPointF(R.right() - R.width() / 2,
                                                R.bottom() - offset)
            self.polygon[self.indexL] = QPointF(self.polygon[self.indexL].x(),
                                                R.bottom() - R.height() / 2)
            self.polygon[self.indexE] = QPointF(self.polygon[self.indexE].x(),
                                                R.bottom() - R.height() / 2)
            self.polygon[self.indexR] = QPointF(R.right() - offset,
                                                R.bottom() - R.height() / 2)

        self.updateHandles()
        self.updateTextPos(moved=moved)
        self.updateAnchors(self.mousePressData, D)

    def width(self):
        """
        Returns the width of the shape.
        :rtype: int
        """
        return self.polygon[self.indexR].x() - self.polygon[self.indexL].x()

    ####################################################################################################################
    #                                                                                                                  #
    #   GEOMETRY                                                                                                       #
    #                                                                                                                  #
    ####################################################################################################################

    def boundingRect(self):
        """
        Returns the shape bounding rectangle.
        :rtype: QRectF
        """
        return self.selection

    def painterPath(self):
        """
        Returns the current shape as QPainterPath (used for collision detection).
        :rtype: QPainterPath
        """
        path = QPainterPath()
        path.addPolygon(self.polygon)
        return path

    def shape(self):
        """
        Returns the shape of this item as a QPainterPath in local coordinates.
        :rtype: QPainterPath
        """
        path = QPainterPath()
        path.addPolygon(self.polygon)
        for shape in self.handleBound:
            path.addEllipse(shape)
        return path

    ####################################################################################################################
    #                                                                                                                  #
    #   LABEL SHORTCUTS                                                                                                #
    #                                                                                                                  #
    ####################################################################################################################

    def textPos(self):
        """
        Returns the current label position in item coordinates.
        :rtype: QPointF
        """
        return self.label.pos()

    def text(self):
        """
        Returns the label text.
        :rtype: str
        """
        return self.label.text()

    def setTextPos(self, pos):
        """
        Set the label position.
        :type pos: QPointF
        """
        self.label.setPos(pos)

    def setText(self, text):
        """
        Set the label text.
        :type text: str
        """
        self.label.editable = Special.forValue(text) is None
        self.label.setText(text)

    def updateTextPos(self, *args, **kwargs):
        """
        Update the label position.
        """
        self.label.updatePos(*args, **kwargs)

    ####################################################################################################################
    #                                                                                                                  #
    #   DRAWING                                                                                                        #
    #                                                                                                                  #
    ####################################################################################################################

    @classmethod
    def image(cls, **kwargs):
        """
        Returns an image suitable for the palette.
        :rtype: QPixmap
        """
        # INITIALIZATION
        pixmap = QPixmap(kwargs['w'], kwargs['h'])
        pixmap.fill(Qt.transparent)
        painter = QPainter(pixmap)
        polygon = cls.createPolygon(46, 34)
        # ITEM SHAPE
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setPen(QPen(QColor(0, 0, 0), 1.1, Qt.SolidLine))
        painter.setBrush(QColor(252, 252, 252))
        painter.translate(kwargs['w'] / 2, kwargs['h'] / 2)
        painter.drawPolygon(polygon)
        # TEXT WITHIN SHAPE
        painter.setFont(Font('Arial', 11, Font.Light))
        painter.drawText(polygon.boundingRect(), Qt.AlignCenter, 'role')
        return pixmap

    def paint(self, painter, option, widget=None):
        """
        Paint the node in the diagram scene.
        :type painter: QPainter
        :type option: QStyleOptionGraphicsItem
        :type widget: QWidget
        """
        # SET THE RECT THAT NEEDS TO BE REPAINTED
        painter.setClipRect(option.exposedRect)
        # SELECTION AREA
        painter.setPen(self.selectionPen)
        painter.setBrush(self.selectionBrush)
        painter.drawRect(self.selection)
        # SYNTAX VALIDATION
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setPen(self.backgroundPen)
        painter.setBrush(self.backgroundBrush)
        painter.drawPolygon(self.background)
        # ITEM SHAPE
        painter.setPen(self.pen)
        painter.setBrush(self.brush)
        painter.drawPolygon(self.polygon)
        # RESIZE HANDLES
        painter.setRenderHint(QPainter.Antialiasing)
        for i in range(self.handleNum):
            painter.setBrush(self.handleBrush[i])
            painter.setPen(self.handlePen[i])
            painter.drawEllipse(self.handleBound[i])
예제 #2
0
class ValueDomainNode(AbstractNode):
    """
    This class implements the 'Value-Domain' node.
    """
    identities = {Identity.ValueDomain}
    item = Item.ValueDomainNode
    minheight = 40
    minwidth = 90

    def __init__(self, width=minwidth, height=minheight, brush=None, **kwargs):
        """
        Initialize the Value-Domain node.
        :type width: int
        :type height: int
        :type brush: QBrush
        """
        super().__init__(**kwargs)
        self.brush = brush or QBrush(QColor(252, 252, 252))
        self.pen = QPen(QColor(0, 0, 0), 1.0, Qt.SolidLine)
        self.polygon = self.createPolygon(self.minwidth, self.minheight)
        self.background = self.createBackground(self.minwidth + 8,
                                                self.minheight + 8)
        self.selection = self.createSelection(self.minwidth + 8,
                                              self.minheight + 8)
        self.label = Label('xsd:string',
                           movable=False,
                           editable=False,
                           parent=self)
        self.updateLayout()

    ####################################################################################################################
    #                                                                                                                  #
    #   PROPERTIES                                                                                                     #
    #                                                                                                                  #
    ####################################################################################################################

    @property
    def datatype(self):
        """
        Returns the datatype associated with this node.
        :rtype: XsdDatatype
        """
        return XsdDatatype.forValue(self.text())

    @property
    def identity(self):
        """
        Returns the identity of the current node.
        :rtype: Identity
        """
        return Identity.ValueDomain

    @identity.setter
    def identity(self, identity):
        """
        Set the identity of the current node.
        :type identity: Identity
        """
        pass

    ####################################################################################################################
    #                                                                                                                  #
    #   INTERFACE                                                                                                      #
    #                                                                                                                  #
    ####################################################################################################################

    def copy(self, scene):
        """
        Create a copy of the current item.
        :type scene: DiagramScene
        """
        kwargs = {
            'id': self.id,
            'brush': self.brush,
            'height': self.height(),
            'width': self.width(),
        }
        node = scene.factory.create(item=self.item, scene=scene, **kwargs)
        node.setPos(self.pos())
        node.setText(self.text())
        node.setTextPos(node.mapFromScene(self.mapToScene(self.textPos())))
        return node

    @staticmethod
    def createBackground(width, height):
        """
        Returns the initialized background polygon according to the given width/height.
        :type width: int
        :type height: int
        :rtype: QRectF
        """
        return QRectF(-width / 2, -height / 2, width, height)

    @staticmethod
    def createPolygon(width, height):
        """
        Returns the initialized polygon according to the given width/height.
        :type width: int
        :type height: int
        :rtype: QRectF
        """
        return QRectF(-width / 2, -height / 2, width, height)

    def height(self):
        """
        Returns the height of the shape.
        :rtype: int
        """
        return self.polygon.height()

    def updateLayout(self):
        """
        Update current shape rect according to the selected datatype.
        """
        width = max(self.label.width() + 16, self.minwidth)
        self.polygon = self.createPolygon(width, self.minheight)
        self.background = self.createBackground(width + 8, self.minheight + 8)
        self.selection = self.createSelection(width + 8, self.minheight + 8)
        self.updateTextPos()
        self.updateEdges()

    def width(self):
        """
        Returns the width of the shape.
        :rtype: int
        """
        return self.polygon.width()

    ####################################################################################################################
    #                                                                                                                  #
    #   GEOMETRY                                                                                                       #
    #                                                                                                                  #
    ####################################################################################################################

    def boundingRect(self):
        """
        Returns the shape bounding rectangle.
        :rtype: QRectF
        """
        return self.selection

    def painterPath(self):
        """
        Returns the current shape as QPainterPath (used for collision detection).
        :rtype: QPainterPath
        """
        path = QPainterPath()
        path.addRoundedRect(self.polygon, 8, 8)
        return path

    def shape(self):
        """
        Returns the shape of this item as a QPainterPath in local coordinates.
        :rtype: QPainterPath
        """
        path = QPainterPath()
        path.addRoundedRect(self.polygon, 8, 8)
        return path

    ####################################################################################################################
    #                                                                                                                  #
    #   LABEL SHORTCUTS                                                                                                #
    #                                                                                                                  #
    ####################################################################################################################

    def textPos(self):
        """
        Returns the current label position in item coordinates.
        :rtype: QPointF
        """
        return self.label.pos()

    def text(self):
        """
        Returns the label text.
        :rtype: str
        """
        return self.label.text()

    def setTextPos(self, pos):
        """
        Set the label position.
        :type pos: QPointF
        """
        self.label.setPos(pos)

    def setText(self, text):
        """
        Set the label text.
        :type text: str
        """
        datatype = XsdDatatype.forValue(text) or XsdDatatype.string
        self.label.setText(datatype.value)
        self.updateLayout()

    def updateTextPos(self, *args, **kwargs):
        """
        Update the label position.
        """
        self.label.updatePos(*args, **kwargs)

    ####################################################################################################################
    #                                                                                                                  #
    #   DRAWING                                                                                                        #
    #                                                                                                                  #
    ####################################################################################################################

    @classmethod
    def image(cls, **kwargs):
        """
        Returns an image suitable for the palette.
        :rtype: QPixmap
        """
        # INITIALIZATION
        pixmap = QPixmap(kwargs['w'], kwargs['h'])
        pixmap.fill(Qt.transparent)
        painter = QPainter(pixmap)
        rect = cls.createPolygon(54, 34)
        # ITEM SHAPE
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setPen(
            QPen(QColor(0, 0, 0), 1.0, Qt.SolidLine, Qt.SquareCap,
                 Qt.RoundJoin))
        painter.setBrush(QColor(252, 252, 252))
        painter.translate(kwargs['w'] / 2, kwargs['h'] / 2)
        painter.drawRoundedRect(rect, 6, 6)
        # TEXT WITHIN THE SHAPE
        painter.setFont(Font('Arial', 10, Font.Light))
        painter.drawText(rect, Qt.AlignCenter, 'xsd:string')
        return pixmap

    def paint(self, painter, option, widget=None):
        """
        Paint the node in the diagram scene.
        :type painter: QPainter
        :type option: QStyleOptionGraphicsItem
        :type widget: QWidget
        """
        # SET THE RECT THAT NEEDS TO BE REPAINTED
        painter.setClipRect(option.exposedRect)
        # SELECTION AREA
        painter.setPen(self.selectionPen)
        painter.setBrush(self.selectionBrush)
        painter.drawRect(self.selection)
        # SYNTAX VALIDATION
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setPen(self.backgroundPen)
        painter.setBrush(self.backgroundBrush)
        painter.drawRoundedRect(self.background, 8, 8)
        # SHAPE
        painter.setBrush(self.brush)
        painter.setPen(self.pen)
        painter.drawRoundedRect(self.polygon, 8, 8)
예제 #3
0
class ConceptNode(AbstractResizableNode):
    """
    This class implements the 'Concept' node.
    """
    identities = {Identity.Concept}
    item = Item.ConceptNode
    minheight = 50
    minwidth = 110

    def __init__(self, width=minwidth, height=minheight, brush=None, **kwargs):
        """
        Initialize the node.
        :type width: int
        :type height: int
        :type brush: QBrush
        """
        super().__init__(**kwargs)

        w = max(width, self.minwidth)
        h = max(height, self.minheight)
        s = self.handleSize

        self.brush = brush or QBrush(QColor(252, 252, 252))
        self.pen = QPen(QColor(0, 0, 0), 1.0, Qt.SolidLine)
        self.polygon = self.createPolygon(w, h)
        self.background = self.createBackground(w + s, h + s)
        self.selection = self.createSelection(w + s, h + s)
        self.label = Label('concept', parent=self)
        self.label.updatePos()
        self.updateHandles()

    ####################################################################################################################
    #                                                                                                                  #
    #   PROPERTIES                                                                                                     #
    #                                                                                                                  #
    ####################################################################################################################

    @property
    def identity(self):
        """
        Returns the identity of the current node.
        :rtype: Identity
        """
        return Identity.Concept

    @identity.setter
    def identity(self, identity):
        """
        Set the identity of the current node.
        :type identity: Identity
        """
        pass

    @property
    def special(self):
        """
        Returns the special type of this node.
        :rtype: Special
        """
        return Special.forValue(self.text())

    ####################################################################################################################
    #                                                                                                                  #
    #   INTERFACE                                                                                                      #
    #                                                                                                                  #
    ####################################################################################################################

    def copy(self, scene):
        """
        Create a copy of the current item.
        :type scene: DiagramScene
        """
        kwargs = {
            'id': self.id,
            'brush': self.brush,
            'height': self.height(),
            'width': self.width(),
        }
        node = scene.factory.create(item=self.item, scene=scene, **kwargs)
        node.setPos(self.pos())
        node.setText(self.text())
        node.setTextPos(node.mapFromScene(self.mapToScene(self.textPos())))
        return node

    @staticmethod
    def createBackground(width, height):
        """
        Returns the initialized background polygon according to the given width/height.
        :type width: int
        :type height: int
        :rtype: QRectF
        """
        return QRectF(-width / 2, -height / 2, width, height)

    @staticmethod
    def createPolygon(width, height):
        """
        Returns the initialized polygon according to the given width/height.
        :type width: int
        :type height: int
        :rtype: QRectF
        """
        return QRectF(-width / 2, -height / 2, width, height)

    def height(self):
        """
        Returns the height of the shape.
        :rtype: int
        """
        return self.polygon.height()

    def interactiveResize(self, mousePos):
        """
        Handle the interactive resize of the shape.
        :type mousePos: QPointF
        """
        scene = self.scene()
        snap = scene.mainwindow.snapToGrid
        size = scene.GridSize
        offset = self.handleSize + self.handleMove
        moved = self.label.moved

        R = QRectF(self.boundingRect())
        D = QPointF(0, 0)

        minBoundW = self.minwidth + offset * 2
        minBoundH = self.minheight + offset * 2

        self.prepareGeometryChange()

        if self.mousePressHandle == self.handleTL:

            fromX = self.mousePressBound.left()
            fromY = self.mousePressBound.top()
            toX = fromX + mousePos.x() - self.mousePressPos.x()
            toY = fromY + mousePos.y() - self.mousePressPos.y()
            toX = snapF(toX, size, -offset, snap)
            toY = snapF(toY, size, -offset, snap)
            D.setX(toX - fromX)
            D.setY(toY - fromY)
            R.setLeft(toX)
            R.setTop(toY)

            ## CLAMP SIZE
            if R.width() < minBoundW:
                D.setX(D.x() - minBoundW + R.width())
                R.setLeft(R.left() - minBoundW + R.width())
            if R.height() < minBoundH:
                D.setY(D.y() - minBoundH + R.height())
                R.setTop(R.top() - minBoundH + R.height())

            self.background.setLeft(R.left())
            self.background.setTop(R.top())
            self.selection.setLeft(R.left())
            self.selection.setTop(R.top())
            self.polygon.setLeft(R.left() + offset)
            self.polygon.setTop(R.top() + offset)

        elif self.mousePressHandle == self.handleTM:

            fromY = self.mousePressBound.top()
            toY = fromY + mousePos.y() - self.mousePressPos.y()
            toY = snapF(toY, size, -offset, snap)
            D.setY(toY - fromY)
            R.setTop(toY)

            ## CLAMP SIZE
            if R.height() < minBoundH:
                D.setY(D.y() - minBoundH + R.height())
                R.setTop(R.top() - minBoundH + R.height())

            self.background.setTop(R.top())
            self.selection.setTop(R.top())
            self.polygon.setTop(R.top() + offset)

        elif self.mousePressHandle == self.handleTR:

            fromX = self.mousePressBound.right()
            fromY = self.mousePressBound.top()
            toX = fromX + mousePos.x() - self.mousePressPos.x()
            toY = fromY + mousePos.y() - self.mousePressPos.y()
            toX = snapF(toX, size, +offset, snap)
            toY = snapF(toY, size, -offset, snap)
            D.setX(toX - fromX)
            D.setY(toY - fromY)
            R.setRight(toX)
            R.setTop(toY)

             ## CLAMP SIZE
            if R.width() < minBoundW:
                D.setX(D.x() + minBoundW - R.width())
                R.setRight(R.right() + minBoundW - R.width())
            if R.height() < minBoundH:
                D.setY(D.y() - minBoundH + R.height())
                R.setTop(R.top() - minBoundH + R.height())

            self.background.setRight(R.right())
            self.background.setTop(R.top())
            self.selection.setRight(R.right())
            self.selection.setTop(R.top())
            self.polygon.setRight(R.right() - offset)
            self.polygon.setTop(R.top() + offset)

        elif self.mousePressHandle == self.handleML:

            fromX = self.mousePressBound.left()
            toX = fromX + mousePos.x() - self.mousePressPos.x()
            toX = snapF(toX, size, -offset, snap)
            D.setX(toX - fromX)
            R.setLeft(toX)

             ## CLAMP SIZE
            if R.width() < minBoundW:
                D.setX(D.x() - minBoundW + R.width())
                R.setLeft(R.left() - minBoundW + R.width())

            self.background.setLeft(R.left())
            self.selection.setLeft(R.left())
            self.polygon.setLeft(R.left() + offset)

        elif self.mousePressHandle == self.handleMR:

            fromX = self.mousePressBound.right()
            toX = fromX + mousePos.x() - self.mousePressPos.x()
            toX = snapF(toX, size, +offset, snap)
            D.setX(toX - fromX)
            R.setRight(toX)

            ## CLAMP SIZE
            if R.width() < minBoundW:
                D.setX(D.x() + minBoundW - R.width())
                R.setRight(R.right() + minBoundW - R.width())

            self.background.setRight(R.right())
            self.selection.setRight(R.right())
            self.polygon.setRight(R.right() - offset)

        elif self.mousePressHandle == self.handleBL:

            fromX = self.mousePressBound.left()
            fromY = self.mousePressBound.bottom()
            toX = fromX + mousePos.x() - self.mousePressPos.x()
            toY = fromY + mousePos.y() - self.mousePressPos.y()
            toX = snapF(toX, size, -offset, snap)
            toY = snapF(toY, size, +offset, snap)
            D.setX(toX - fromX)
            D.setY(toY - fromY)
            R.setLeft(toX)
            R.setBottom(toY)

            ## CLAMP SIZE
            if R.width() < minBoundW:
                D.setX(D.x() - minBoundW + R.width())
                R.setLeft(R.left() - minBoundW + R.width())
            if R.height() < minBoundH:
                D.setY(D.y() + minBoundH - R.height())
                R.setBottom(R.bottom() + minBoundH - R.height())

            self.background.setLeft(R.left())
            self.background.setBottom(R.bottom())
            self.selection.setLeft(R.left())
            self.selection.setBottom(R.bottom())
            self.polygon.setLeft(R.left() + offset)
            self.polygon.setBottom(R.bottom() - offset)

        elif self.mousePressHandle == self.handleBM:

            fromY = self.mousePressBound.bottom()
            toY = fromY + mousePos.y() - self.mousePressPos.y()
            toY = snapF(toY, size, +offset, snap)
            D.setY(toY - fromY)
            R.setBottom(toY)

            ## CLAMP SIZE
            if R.height() < minBoundH:
                D.setY(D.y() + minBoundH - R.height())
                R.setBottom(R.bottom() + minBoundH - R.height())

            self.background.setBottom(R.bottom())
            self.selection.setBottom(R.bottom())
            self.polygon.setBottom(R.bottom() - offset)

        elif self.mousePressHandle == self.handleBR:

            fromX = self.mousePressBound.right()
            fromY = self.mousePressBound.bottom()
            toX = fromX + mousePos.x() - self.mousePressPos.x()
            toY = fromY + mousePos.y() - self.mousePressPos.y()
            toX = snapF(toX, size, +offset, snap)
            toY = snapF(toY, size, +offset, snap)
            D.setX(toX - fromX)
            D.setY(toY - fromY)
            R.setRight(toX)
            R.setBottom(toY)

            ## CLAMP SIZE
            if R.width() < minBoundW:
                D.setX(D.x() + minBoundW - R.width())
                R.setRight(R.right() + minBoundW - R.width())
            if R.height() < minBoundH:
                D.setY(D.y() + minBoundH - R.height())
                R.setBottom(R.bottom() + minBoundH - R.height())

            self.background.setRight(R.right())
            self.background.setBottom(R.bottom())
            self.selection.setRight(R.right())
            self.selection.setBottom(R.bottom())
            self.polygon.setRight(R.right() - offset)
            self.polygon.setBottom(R.bottom() - offset)

        self.updateHandles()
        self.updateTextPos(moved=moved)
        self.updateAnchors(self.mousePressData, D)

    def width(self):
        """
        Returns the width of the shape.
        :rtype: int
        """
        return self.polygon.width()

    ####################################################################################################################
    #                                                                                                                  #
    #   GEOMETRY                                                                                                       #
    #                                                                                                                  #
    ####################################################################################################################

    def boundingRect(self):
        """
        Returns the shape bounding rectangle.
        :rtype: QRectF
        """
        return self.selection

    def painterPath(self):
        """
        Returns the current shape as QPainterPath (used for collision detection).
        :rtype: QPainterPath
        """
        path = QPainterPath()
        path.addRect(self.polygon)
        return path

    def shape(self):
        """
        Returns the shape of this item as a QPainterPath in local coordinates.
        :rtype: QPainterPath
        """
        path = QPainterPath()
        path.addRect(self.polygon)
        for shape in self.handleBound:
            path.addEllipse(shape)
        return path

    ####################################################################################################################
    #                                                                                                                  #
    #   LABEL SHORTCUTS                                                                                                #
    #                                                                                                                  #
    ####################################################################################################################

    def textPos(self):
        """
        Returns the current label position in item coordinates.
        :rtype: QPointF
        """
        return self.label.pos()

    def text(self):
        """
        Returns the label text.
        :rtype: str
        """
        return self.label.text()

    def setTextPos(self, pos):
        """
        Set the label position.
        :type pos: QPointF
        """
        self.label.setPos(pos)

    def setText(self, text):
        """
        Set the label text.
        :type text: str
        """
        self.label.editable = Special.forValue(text) is None
        self.label.setText(text)

    def updateTextPos(self, *args, **kwargs):
        """
        Update the label position.
        """
        self.label.updatePos(*args, **kwargs)

    ####################################################################################################################
    #                                                                                                                  #
    #   DRAWING                                                                                                        #
    #                                                                                                                  #
    ####################################################################################################################

    @classmethod
    def image(cls, **kwargs):
        """
        Returns an image suitable for the palette.
        :rtype: QPixmap
        """
        # INITIALIZATION
        pixmap = QPixmap(kwargs['w'], kwargs['h'])
        pixmap.fill(Qt.transparent)
        painter = QPainter(pixmap)
        rect = cls.createPolygon(54, 34)
        # DRAW THE RECTANGLE
        painter.setPen(QPen(QColor(0, 0, 0), 1.0, Qt.SolidLine))
        painter.setBrush(QColor(252, 252, 252))
        painter.translate(kwargs['w'] / 2, kwargs['h'] / 2)
        painter.drawRect(rect)
        # TEXT WITHIN THE RECTANGLE
        painter.setFont(Font('Arial', 11, Font.Light))
        painter.drawText(rect, Qt.AlignCenter, 'concept')
        return pixmap

    def paint(self, painter, option, widget=None):
        """
        Paint the node in the diagram scene.
        :type painter: QPainter
        :type option: QStyleOptionGraphicsItem
        :type widget: QWidget
        """
        # SET THE RECT THAT NEEDS TO BE REPAINTED
        painter.setClipRect(option.exposedRect)
        # SELECTION AREA
        painter.setPen(self.selectionPen)
        painter.setBrush(self.selectionBrush)
        painter.drawRect(self.selection)
        # SYNTAX VALIDATION
        painter.setPen(self.backgroundPen)
        painter.setBrush(self.backgroundBrush)
        painter.drawRect(self.background)
        # ITEM SHAPE
        painter.setPen(self.pen)
        painter.setBrush(self.brush)
        painter.drawRect(self.polygon)
        # RESIZE HANDLES
        painter.setRenderHint(QPainter.Antialiasing)
        for i in range(self.handleNum):
            painter.setBrush(self.handleBrush[i])
            painter.setPen(self.handlePen[i])
            painter.drawEllipse(self.handleBound[i])
예제 #4
0
class AttributeNode(AbstractNode):
    """
    This class implements the 'Attribute' node.
    """
    identities = {Identity.Attribute}
    item = Item.AttributeNode

    def __init__(self, width=20, height=20, brush=None, **kwargs):
        """
        Initialize the node.
        :type width: int
        :type height: int
        :type brush: QBrush
        """
        super().__init__(**kwargs)
        self.brush = brush or QBrush(QColor(252, 252, 252))
        self.pen = QPen(QColor(0, 0, 0), 1.1, Qt.SolidLine)
        self.polygon = self.createPolygon(20, 20)
        self.background = self.createBackground(28, 28)
        self.selection = self.createBackground(28, 28)
        self.label = Label('attribute', centered=False, parent=self)
        self.label.updatePos()

    ####################################################################################################################
    #                                                                                                                  #
    #   PROPERTIES                                                                                                     #
    #                                                                                                                  #
    ####################################################################################################################

    @property
    def functional(self):
        """
        Tells whether the Attribute is defined as functional.
        :rtype: bool
        """
        scene = self.scene()
        meta = scene.meta.metaFor(self.item, self.text())
        return meta.functionality

    @functional.setter
    def functional(self, value):
        """
        Set the Attribute functionality property.
        :type value: bool
        """
        scene = self.scene()
        meta = scene.meta.metaFor(self.item, self.text())
        meta.functionality = bool(value)
        scene.meta.add(self.item, self.text(), meta)

    @property
    def identity(self):
        """
        Returns the identity of the current node.
        :rtype: Identity
        """
        return Identity.Attribute

    @identity.setter
    def identity(self, identity):
        """
        Set the identity of the current node.
        :type identity: Identity
        """
        pass

    @property
    def special(self):
        """
        Returns the special type of this node.
        :rtype: Special
        """
        return Special.forValue(self.text())

    ####################################################################################################################
    #                                                                                                                  #
    #   INTERFACE                                                                                                      #
    #                                                                                                                  #
    ####################################################################################################################

    def copy(self, scene):
        """
        Create a copy of the current item.
        :type scene: DiagramScene
        """
        kwargs = {
            'id': self.id,
            'brush': self.brush,
            'height': self.height(),
            'width': self.width(),
        }
        node = scene.factory.create(item=self.item, scene=scene, **kwargs)
        node.setPos(self.pos())
        node.setText(self.text())
        node.setTextPos(node.mapFromScene(self.mapToScene(self.textPos())))
        return node

    @staticmethod
    def createBackground(width, height):
        """
        Returns the initialized background polygon according to the given width/height.
        :type width: int
        :type height: int
        :rtype: T QRectF
        """
        return QRectF(-width / 2, -height / 2, width, height)

    @staticmethod
    def createPolygon(width, height):
        """
        Returns the initialized polygon according to the given width/height.
        :type width: int
        :type height: int
        :rtype: QRectF
        """
        return QRectF(-width / 2, -height / 2, width, height)

    def height(self):
        """
        Returns the height of the shape.
        :rtype: int
        """
        return self.polygon.height()

    def width(self):
        """
        Returns the width of the shape.
        :rtype: int
        """
        return self.polygon.width()

    ####################################################################################################################
    #                                                                                                                  #
    #   GEOMETRY                                                                                                       #
    #                                                                                                                  #
    ####################################################################################################################

    def boundingRect(self):
        """
        Returns the shape bounding rectangle.
        :rtype: QRectF
        """
        return self.selection

    def painterPath(self):
        """
        Returns the current shape as QPainterPath (used for collision detection).
        :rtype: QPainterPath
        """
        path = QPainterPath()
        path.addEllipse(self.polygon)
        return path

    def shape(self):
        """
        Returns the shape of this item as a QPainterPath in local coordinates.
        :rtype: QPainterPath
        """
        path = QPainterPath()
        path.addEllipse(self.polygon)
        return path

    ####################################################################################################################
    #                                                                                                                  #
    #   LABEL SHORTCUTS                                                                                                #
    #                                                                                                                  #
    ####################################################################################################################

    def textPos(self):
        """
        Returns the current label position in item coordinates.
        :rtype: QPointF
        """
        return self.label.pos()

    def text(self):
        """
        Returns the label text.
        :rtype: str
        """
        return self.label.text()

    def setTextPos(self, pos):
        """
        Set the label position.
        :type pos: QPointF
        """
        self.label.setPos(pos)

    def setText(self, text):
        """
        Set the label text.
        :type text: str
        """
        self.label.editable = Special.forValue(text) is None
        self.label.setText(text)

    def updateTextPos(self, *args, **kwargs):
        """
        Update the label position.
        """
        self.label.updatePos(*args, **kwargs)

    ####################################################################################################################
    #                                                                                                                  #
    #   DRAWING                                                                                                        #
    #                                                                                                                  #
    ####################################################################################################################

    @classmethod
    def image(cls, **kwargs):
        """
        Returns an image suitable for the palette.
        :rtype: QPixmap
        """
        # INITIALIZATION
        pixmap = QPixmap(kwargs['w'], kwargs['h'])
        pixmap.fill(Qt.transparent)
        painter = QPainter(pixmap)
        # TEXT
        painter.setFont(Font('Arial', 9, Font.Light))
        painter.translate(0, 0)
        painter.drawText(QRectF(0, 0, kwargs['w'], kwargs['h'] / 2),
                         Qt.AlignCenter, 'attribute')
        # ITEM SHAPE
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setPen(QPen(QColor(0, 0, 0), 1.1, Qt.SolidLine))
        painter.setBrush(QColor(252, 252, 252))
        painter.translate(kwargs['w'] / 2, kwargs['h'] / 2 + 6)
        painter.drawEllipse(cls.createPolygon(18, 18))
        return pixmap

    def paint(self, painter, option, widget=None):
        """
        Paint the node in the diagram scene.
        :type painter: QPainter
        :type option: QStyleOptionGraphicsItem
        :type widget: QWidget
        """
        # SET THE RECT THAT NEEDS TO BE REPAINTED
        painter.setClipRect(option.exposedRect)
        # SELECTION AREA
        painter.setPen(self.selectionPen)
        painter.setBrush(self.selectionBrush)
        painter.drawRect(self.selection)
        # SYNTAX VALIDATION
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setPen(self.backgroundPen)
        painter.setBrush(self.backgroundBrush)
        painter.drawEllipse(self.background)
        # ITEM SHAPE
        painter.setPen(self.pen)
        painter.setBrush(self.brush)
        painter.drawEllipse(self.polygon)
예제 #5
0
class RestrictionNode(AbstractNode):
    """
    This is the base class for all the Restriction nodes.
    """
    __metaclass__ = ABCMeta

    def __init__(self, width=20, height=20, brush=None, **kwargs):
        """
        Initialize the node.
        :type width: int
        :type height: int
        :type brush: QBrush
        """
        super().__init__(**kwargs)
        self.brush = brush or QBrush(QColor(252, 252, 252))
        self.pen = QPen(QColor(0, 0, 0), 1.0, Qt.SolidLine)
        self.polygon = self.createPolygon(20, 20)
        self.background = self.createBackground(28, 28)
        self.selection = self.createSelection(28, 28)
        self.label = Label(Restriction.Exists.label, centered=False, editable=False, parent=self)
        self.label.updatePos()

    ####################################################################################################################
    #                                                                                                                  #
    #   PROPERTIES                                                                                                     #
    #                                                                                                                  #
    ####################################################################################################################

    @property
    def identity(self):
        """
        Returns the identity of the current node.
        :rtype: Identity
        """
        return Identity.Concept

    @identity.setter
    def identity(self, identity):
        """
        Set the identity of the current node.
        :type identity: Identity
        """
        pass

    @property
    def cardinality(self):
        """
        Returns the cardinality of the node.
        :rtype: dict
        """
        cardinality = {'min': None, 'max': None}
        match = RE_CARDINALITY.match(self.text())
        if match:
            if match.group('min') != '-':
                cardinality['min'] = int(match.group('min'))
            if match.group('max') != '-':
                cardinality['max'] = int(match.group('max'))
        return cardinality

    @property
    def restriction(self):
        """
        Returns the restriction type of the node.
        :rtype: Restriction
        """
        return Restriction.forLabel(self.text())

    ####################################################################################################################
    #                                                                                                                  #
    #   INTERFACE                                                                                                      #
    #                                                                                                                  #
    ####################################################################################################################

    def copy(self, scene):
        """
        Create a copy of the current item.
        :type scene: DiagramScene
        """
        kwargs = {
            'id': self.id,
            'height': self.height(),
            'width': self.width(),
        }
        node = scene.factory.create(item=self.item, scene=scene, **kwargs)
        node.setPos(self.pos())
        node.setText(self.text())
        node.setTextPos(node.mapFromScene(self.mapToScene(self.textPos())))
        return node

    @staticmethod
    def createBackground(width, height):
        """
        Returns the initialized background polygon according to the given width/height.
        :type width: int
        :type height: int
        :rtype: QRectF
        """
        return QRectF(-width / 2, -height / 2, width, height)

    @staticmethod
    def createPolygon(width, height):
        """
        Returns the initialized polygon according to the given width/height.
        :type width: int
        :type height: int
        :rtype: QRectF
        """
        return QRectF(-width / 2, -height / 2, width, height)

    def height(self):
        """
        Returns the height of the shape.
        :rtype: int
        """
        return self.polygon.height()

    def width(self):
        """
        Returns the width of the shape.
        :rtype: int
        """
        return self.polygon.width()

    ####################################################################################################################
    #                                                                                                                  #
    #   GEOMETRY                                                                                                       #
    #                                                                                                                  #
    ####################################################################################################################

    def boundingRect(self):
        """
        Returns the shape bounding rectangle.
        :rtype: QRectF
        """
        return self.selection

    def painterPath(self):
        """
        Returns the current shape as QPainterPath (used for collision detection).
        :rtype: QPainterPath
        """
        path = QPainterPath()
        path.addRect(self.polygon)
        return path

    def shape(self, *args, **kwargs):
        """
        Returns the shape of this item as a QPainterPath in local coordinates.
        :rtype: QPainterPath
        """
        path = QPainterPath()
        path.addRect(self.polygon)
        return path

    ####################################################################################################################
    #                                                                                                                  #
    #   LABEL SHORTCUTS                                                                                                #
    #                                                                                                                  #
    ####################################################################################################################

    def textPos(self):
        """
        Returns the current label position.
        :rtype: QPointF
        """
        return self.label.pos()

    def text(self):
        """
        Returns the label text.
        :rtype: str
        """
        return self.label.text()

    def setTextPos(self, pos):
        """
        Set the label position.
        :type pos: QPointF
        """
        self.label.setPos(pos)

    def setText(self, text):
        """
        Set the label text: will additionally parse the given value checking for a consistent restriction type.
        :type text: str
        """
        restriction = Restriction.forLabel(text)
        if not restriction:
            text = Restriction.Exists.label
        self.label.setText(text)

    def updateTextPos(self, *args, **kwargs):
        """
        Update the label position.
        """
        self.label.updatePos(*args, **kwargs)

    ####################################################################################################################
    #                                                                                                                  #
    #   DRAWING                                                                                                        #
    #                                                                                                                  #
    ####################################################################################################################

    @classmethod
    @abstractmethod
    def image(cls, **kwargs):
        """
        Returns an image suitable for the palette.
        :rtype: QPixmap
        """
        pass

    def paint(self, painter, option, widget=None):
        """
        Paint the node in the diagram scene.
        :type painter: QPainter
        :type option: QStyleOptionGraphicsItem
        :type widget: QWidget
        """
        # SELECTION AREA
        painter.setPen(self.selectionPen)
        painter.setBrush(self.selectionBrush)
        painter.drawRect(self.selection)
        # SYNTAX VALIDATION
        painter.setPen(self.backgroundPen)
        painter.setBrush(self.backgroundBrush)
        painter.drawRect(self.background)
        # SHAPE
        painter.setPen(self.pen)
        painter.setBrush(self.brush)
        painter.drawRect(self.polygon)
예제 #6
0
class AttributeNode(AbstractNode):
    """
    This class implements the 'Attribute' node.
    """
    identities = {Identity.Attribute}
    item = Item.AttributeNode

    def __init__(self, width=20, height=20, brush=None, **kwargs):
        """
        Initialize the node.
        :type width: int
        :type height: int
        :type brush: QBrush
        """
        super().__init__(**kwargs)
        self.brush = brush or QBrush(QColor(252, 252, 252))
        self.pen = QPen(QColor(0, 0, 0), 1.1, Qt.SolidLine)
        self.polygon = self.createPolygon(20, 20)
        self.background = self.createBackground(28, 28)
        self.selection = self.createBackground(28, 28)
        self.label = Label('attribute', centered=False, parent=self)
        self.label.updatePos()

    ####################################################################################################################
    #                                                                                                                  #
    #   PROPERTIES                                                                                                     #
    #                                                                                                                  #
    ####################################################################################################################

    @property
    def functional(self):
        """
        Tells whether the Attribute is defined as functional.
        :rtype: bool
        """
        scene = self.scene()
        meta = scene.meta.metaFor(self.item, self.text())
        return meta.functionality

    @functional.setter
    def functional(self, value):
        """
        Set the Attribute functionality property.
        :type value: bool
        """
        scene = self.scene()
        meta = scene.meta.metaFor(self.item, self.text())
        meta.functionality = bool(value)
        scene.meta.add(self.item, self.text(), meta)

    @property
    def identity(self):
        """
        Returns the identity of the current node.
        :rtype: Identity
        """
        return Identity.Attribute

    @identity.setter
    def identity(self, identity):
        """
        Set the identity of the current node.
        :type identity: Identity
        """
        pass

    @property
    def special(self):
        """
        Returns the special type of this node.
        :rtype: Special
        """
        return Special.forValue(self.text())

    ####################################################################################################################
    #                                                                                                                  #
    #   INTERFACE                                                                                                      #
    #                                                                                                                  #
    ####################################################################################################################

    def copy(self, scene):
        """
        Create a copy of the current item.
        :type scene: DiagramScene
        """
        kwargs = {
            'id': self.id,
            'brush': self.brush,
            'height': self.height(),
            'width': self.width(),
        }
        node = scene.factory.create(item=self.item, scene=scene, **kwargs)
        node.setPos(self.pos())
        node.setText(self.text())
        node.setTextPos(node.mapFromScene(self.mapToScene(self.textPos())))
        return node

    @staticmethod
    def createBackground(width, height):
        """
        Returns the initialized background polygon according to the given width/height.
        :type width: int
        :type height: int
        :rtype: T QRectF
        """
        return QRectF(-width / 2, -height / 2, width, height)

    @staticmethod
    def createPolygon(width, height):
        """
        Returns the initialized polygon according to the given width/height.
        :type width: int
        :type height: int
        :rtype: QRectF
        """
        return QRectF(-width / 2, -height / 2, width, height)

    def height(self):
        """
        Returns the height of the shape.
        :rtype: int
        """
        return self.polygon.height()

    def width(self):
        """
        Returns the width of the shape.
        :rtype: int
        """
        return self.polygon.width()

    ####################################################################################################################
    #                                                                                                                  #
    #   GEOMETRY                                                                                                       #
    #                                                                                                                  #
    ####################################################################################################################

    def boundingRect(self):
        """
        Returns the shape bounding rectangle.
        :rtype: QRectF
        """
        return self.selection

    def painterPath(self):
        """
        Returns the current shape as QPainterPath (used for collision detection).
        :rtype: QPainterPath
        """
        path = QPainterPath()
        path.addEllipse(self.polygon)
        return path

    def shape(self):
        """
        Returns the shape of this item as a QPainterPath in local coordinates.
        :rtype: QPainterPath
        """
        path = QPainterPath()
        path.addEllipse(self.polygon)
        return path

    ####################################################################################################################
    #                                                                                                                  #
    #   LABEL SHORTCUTS                                                                                                #
    #                                                                                                                  #
    ####################################################################################################################

    def textPos(self):
        """
        Returns the current label position in item coordinates.
        :rtype: QPointF
        """
        return self.label.pos()

    def text(self):
        """
        Returns the label text.
        :rtype: str
        """
        return self.label.text()

    def setTextPos(self, pos):
        """
        Set the label position.
        :type pos: QPointF
        """
        self.label.setPos(pos)

    def setText(self, text):
        """
        Set the label text.
        :type text: str
        """
        self.label.editable = Special.forValue(text) is None
        self.label.setText(text)

    def updateTextPos(self, *args, **kwargs):
        """
        Update the label position.
        """
        self.label.updatePos(*args, **kwargs)

    ####################################################################################################################
    #                                                                                                                  #
    #   DRAWING                                                                                                        #
    #                                                                                                                  #
    ####################################################################################################################

    @classmethod
    def image(cls, **kwargs):
        """
        Returns an image suitable for the palette.
        :rtype: QPixmap
        """
        # INITIALIZATION
        pixmap = QPixmap(kwargs['w'], kwargs['h'])
        pixmap.fill(Qt.transparent)
        painter = QPainter(pixmap)
        # TEXT
        painter.setFont(Font('Arial', 9, Font.Light))
        painter.translate(0, 0)
        painter.drawText(QRectF(0, 0, kwargs['w'], kwargs['h'] / 2), Qt.AlignCenter, 'attribute')
        # ITEM SHAPE
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setPen(QPen(QColor(0, 0, 0), 1.1, Qt.SolidLine))
        painter.setBrush(QColor(252, 252, 252))
        painter.translate(kwargs['w'] / 2, kwargs['h'] / 2 + 6)
        painter.drawEllipse(cls.createPolygon(18, 18))
        return pixmap

    def paint(self, painter, option, widget=None):
        """
        Paint the node in the diagram scene.
        :type painter: QPainter
        :type option: QStyleOptionGraphicsItem
        :type widget: QWidget
        """
        # SET THE RECT THAT NEEDS TO BE REPAINTED
        painter.setClipRect(option.exposedRect)
        # SELECTION AREA
        painter.setPen(self.selectionPen)
        painter.setBrush(self.selectionBrush)
        painter.drawRect(self.selection)
        # SYNTAX VALIDATION
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setPen(self.backgroundPen)
        painter.setBrush(self.backgroundBrush)
        painter.drawEllipse(self.background)
        # ITEM SHAPE
        painter.setPen(self.pen)
        painter.setBrush(self.brush)
        painter.drawEllipse(self.polygon)
예제 #7
0
class ValueDomainNode(AbstractNode):
    """
    This class implements the 'Value-Domain' node.
    """
    identities = {Identity.ValueDomain}
    item = Item.ValueDomainNode
    minheight = 40
    minwidth = 90

    def __init__(self, width=minwidth, height=minheight, brush=None, **kwargs):
        """
        Initialize the Value-Domain node.
        :type width: int
        :type height: int
        :type brush: QBrush
        """
        super().__init__(**kwargs)
        self.brush = brush or QBrush(QColor(252, 252, 252))
        self.pen = QPen(QColor(0, 0, 0), 1.0, Qt.SolidLine)
        self.polygon = self.createPolygon(self.minwidth, self.minheight)
        self.background = self.createBackground(self.minwidth + 8, self.minheight + 8)
        self.selection = self.createSelection(self.minwidth + 8, self.minheight + 8)
        self.label = Label('xsd:string', movable=False, editable=False, parent=self)
        self.updateLayout()

    ####################################################################################################################
    #                                                                                                                  #
    #   PROPERTIES                                                                                                     #
    #                                                                                                                  #
    ####################################################################################################################

    @property
    def datatype(self):
        """
        Returns the datatype associated with this node.
        :rtype: XsdDatatype
        """
        return XsdDatatype.forValue(self.text())

    @property
    def identity(self):
        """
        Returns the identity of the current node.
        :rtype: Identity
        """
        return Identity.ValueDomain

    @identity.setter
    def identity(self, identity):
        """
        Set the identity of the current node.
        :type identity: Identity
        """
        pass

    ####################################################################################################################
    #                                                                                                                  #
    #   INTERFACE                                                                                                      #
    #                                                                                                                  #
    ####################################################################################################################

    def copy(self, scene):
        """
        Create a copy of the current item.
        :type scene: DiagramScene
        """
        kwargs = {
            'id': self.id,
            'brush': self.brush,
            'height': self.height(),
            'width': self.width(),
        }
        node = scene.factory.create(item=self.item, scene=scene, **kwargs)
        node.setPos(self.pos())
        node.setText(self.text())
        node.setTextPos(node.mapFromScene(self.mapToScene(self.textPos())))
        return node

    @staticmethod
    def createBackground(width, height):
        """
        Returns the initialized background polygon according to the given width/height.
        :type width: int
        :type height: int
        :rtype: QRectF
        """
        return QRectF(-width / 2, -height / 2, width, height)

    @staticmethod
    def createPolygon(width, height):
        """
        Returns the initialized polygon according to the given width/height.
        :type width: int
        :type height: int
        :rtype: QRectF
        """
        return QRectF(-width / 2, -height / 2, width, height)

    def height(self):
        """
        Returns the height of the shape.
        :rtype: int
        """
        return self.polygon.height()

    def updateLayout(self):
        """
        Update current shape rect according to the selected datatype.
        """
        width = max(self.label.width() + 16, self.minwidth)
        self.polygon = self.createPolygon(width, self.minheight)
        self.background = self.createBackground(width + 8, self.minheight + 8)
        self.selection = self.createSelection(width + 8, self.minheight + 8)
        self.updateTextPos()
        self.updateEdges()

    def width(self):
        """
        Returns the width of the shape.
        :rtype: int
        """
        return self.polygon.width()

    ####################################################################################################################
    #                                                                                                                  #
    #   GEOMETRY                                                                                                       #
    #                                                                                                                  #
    ####################################################################################################################

    def boundingRect(self):
        """
        Returns the shape bounding rectangle.
        :rtype: QRectF
        """
        return self.selection

    def painterPath(self):
        """
        Returns the current shape as QPainterPath (used for collision detection).
        :rtype: QPainterPath
        """
        path = QPainterPath()
        path.addRoundedRect(self.polygon, 8, 8)
        return path

    def shape(self):
        """
        Returns the shape of this item as a QPainterPath in local coordinates.
        :rtype: QPainterPath
        """
        path = QPainterPath()
        path.addRoundedRect(self.polygon, 8, 8)
        return path

    ####################################################################################################################
    #                                                                                                                  #
    #   LABEL SHORTCUTS                                                                                                #
    #                                                                                                                  #
    ####################################################################################################################

    def textPos(self):
        """
        Returns the current label position in item coordinates.
        :rtype: QPointF
        """
        return self.label.pos()

    def text(self):
        """
        Returns the label text.
        :rtype: str
        """
        return self.label.text()

    def setTextPos(self, pos):
        """
        Set the label position.
        :type pos: QPointF
        """
        self.label.setPos(pos)

    def setText(self, text):
        """
        Set the label text.
        :type text: str
        """
        datatype = XsdDatatype.forValue(text) or XsdDatatype.string
        self.label.setText(datatype.value)
        self.updateLayout()

    def updateTextPos(self, *args, **kwargs):
        """
        Update the label position.
        """
        self.label.updatePos(*args, **kwargs)

    ####################################################################################################################
    #                                                                                                                  #
    #   DRAWING                                                                                                        #
    #                                                                                                                  #
    ####################################################################################################################

    @classmethod
    def image(cls, **kwargs):
        """
        Returns an image suitable for the palette.
        :rtype: QPixmap
        """
        # INITIALIZATION
        pixmap = QPixmap(kwargs['w'], kwargs['h'])
        pixmap.fill(Qt.transparent)
        painter = QPainter(pixmap)
        rect = cls.createPolygon(54, 34)
        # ITEM SHAPE
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setPen(QPen(QColor(0, 0, 0), 1.0, Qt.SolidLine, Qt.SquareCap, Qt.RoundJoin))
        painter.setBrush(QColor(252, 252, 252))
        painter.translate(kwargs['w'] / 2, kwargs['h'] / 2)
        painter.drawRoundedRect(rect, 6, 6)
        # TEXT WITHIN THE SHAPE
        painter.setFont(Font('Arial', 10, Font.Light))
        painter.drawText(rect, Qt.AlignCenter, 'xsd:string')
        return pixmap

    def paint(self, painter, option, widget=None):
        """
        Paint the node in the diagram scene.
        :type painter: QPainter
        :type option: QStyleOptionGraphicsItem
        :type widget: QWidget
        """
        # SET THE RECT THAT NEEDS TO BE REPAINTED
        painter.setClipRect(option.exposedRect)
        # SELECTION AREA
        painter.setPen(self.selectionPen)
        painter.setBrush(self.selectionBrush)
        painter.drawRect(self.selection)
        # SYNTAX VALIDATION
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setPen(self.backgroundPen)
        painter.setBrush(self.backgroundBrush)
        painter.drawRoundedRect(self.background, 8, 8)
        # SHAPE
        painter.setBrush(self.brush)
        painter.setPen(self.pen)
        painter.drawRoundedRect(self.polygon, 8, 8)
예제 #8
0
class ValueRestrictionNode(AbstractNode):
    """
    This class implements the 'Value-Restriction' node.
    """
    indexTR = 0
    indexTL = 1
    indexBL = 2
    indexBR = 3
    indexRT = 4
    indexEE = 5

    identities = {Identity.ValueDomain}
    item = Item.ValueRestrictionNode
    minheight = 50
    minwidth = 180

    def __init__(self, width=minwidth, height=minheight, brush=None, **kwargs):
        """
        Initialize the node.
        :type width: int
        :type height: int
        :type brush: QBrush
        """
        super().__init__(**kwargs)
        self.brush = brush or QBrush(QColor(252, 252, 252))
        self.pen = QPen(QColor(0, 0, 0), 1.0, Qt.SolidLine)
        self.polygon = self.createPolygon(self.minwidth, self.minheight)
        self.fold = self.createFold(self.polygon, self.indexTR, self.indexRT)
        self.background = self.createBackground(self.minwidth + 8,
                                                self.minheight + 8)
        self.selection = self.createSelection(self.minwidth + 8,
                                              self.minheight + 8)
        self.label = Label('xsd:length "32"^^xsd:string',
                           movable=False,
                           editable=False,
                           parent=self)
        self.updateLayout()

    ####################################################################################################################
    #                                                                                                                  #
    #   PROPERTIES                                                                                                     #
    #                                                                                                                  #
    ####################################################################################################################

    @property
    def constrained(self):
        """
        Tells whether the datatype of this restriction is constrained by graph composition.
        :rtype: bool
        """
        f1 = lambda x: x.isItem(Item.InputEdge)
        f2 = lambda x: x.isItem(Item.DatatypeRestrictionNode)
        f3 = lambda x: x.isItem(Item.ValueDomainNode)
        x = first(self.outgoingNodes(filter_on_edges=f1, filter_on_nodes=f2))
        if x:
            return first(
                x.incomingNodes(filter_on_edges=f1,
                                filter_on_nodes=f3)) is not None
        return False

    @property
    def datatype(self):
        """
        Returns the datatype associated with this node.
        :rtype: XsdDatatype
        """
        match = RE_FACET.match(self.text())
        if match:
            return XsdDatatype.forValue(match.group('datatype'))
        return None

    @property
    def facet(self):
        """
        Returns the facet associated with this node.
        :rtype: Facet
        """
        match = RE_FACET.match(self.text())
        if match:
            return Facet.forValue(match.group('facet'))
        return None

    @property
    def identity(self):
        """
        Returns the identity of the current node.
        :rtype: Identity
        """
        return Identity.ValueDomain

    @identity.setter
    def identity(self, identity):
        """
        Set the identity of the current node.
        :type identity: Identity
        """
        pass

    @property
    def value(self):
        """
        Returns the value of the restriction.
        :rtype: str
        """
        match = RE_FACET.match(self.text())
        if match:
            return match.group('value')
        return ''

    ####################################################################################################################
    #                                                                                                                  #
    #   INTERFACE                                                                                                      #
    #                                                                                                                  #
    ####################################################################################################################

    @staticmethod
    def compose(facet, value, datatype):
        """
        Compose the restriction string.
        :type facet: Facet
        :type value: str
        :type datatype: XsdDatatype
        :return: str
        """
        return '{} "{}"^^{}'.format(facet.value,
                                    cutR(cutL(value.strip(), '"'), '"'),
                                    datatype.value)

    def copy(self, scene):
        """
        Create a copy of the current item.
        :type scene: DiagramScene
        """
        kwargs = {
            'id': self.id,
            'brush': self.brush,
            'height': self.height(),
            'width': self.width(),
        }
        node = scene.factory.create(item=self.item, scene=scene, **kwargs)
        node.setPos(self.pos())
        node.setText(self.text())
        node.setTextPos(node.mapFromScene(self.mapToScene(self.textPos())))
        return node

    @staticmethod
    def createBackground(width, height):
        """
        Returns the initialized background polygon according to the given width/height.
        :type width: int
        :type height: int
        :rtype: QRectF
        """
        return QRectF(-width / 2, -height / 2, width, height)

    @staticmethod
    def createFold(polygon, indexTR, indexRT):
        """
        Returns the initialized fold polygon.
        :type polygon: QPolygonF
        :type indexTR: int
        :type indexRT: int
        :rtype: QPolygonF
        """
        return QPolygonF([
            QPointF(polygon[indexTR].x(), polygon[indexTR].y()),
            QPointF(polygon[indexTR].x(), polygon[indexTR].y() + 12),
            QPointF(polygon[indexRT].x(), polygon[indexRT].y()),
            QPointF(polygon[indexTR].x(), polygon[indexTR].y()),
        ])

    @staticmethod
    def createPolygon(width, height):
        """
        Returns the initialized polygon according to the given width/height.
        :type width: int
        :type height: int
        :rtype: QPolygonF
        """
        return QPolygonF([
            QPointF(+(width / 2) - 12, -(height / 2)),  # 0
            QPointF(-(width / 2), -(height / 2)),  # 1
            QPointF(-(width / 2), +(height / 2)),  # 2
            QPointF(+(width / 2), +(height / 2)),  # 3
            QPointF(+(width / 2), -(height / 2) + 12),  # 4
            QPointF(+(width / 2) - 12, -(height / 2)),  # 5
        ])

    def height(self):
        """
        Returns the height of the shape.
        :rtype: int
        """
        return self.polygon[self.indexBL].y() - self.polygon[self.indexTL].y()

    def updateLayout(self):
        """
        Update current shape rect according to the selected datatype.
        """
        width = max(self.label.width() + 16, self.minwidth)
        self.polygon = self.createPolygon(width, self.minheight)
        self.fold = self.createFold(self.polygon, self.indexTR, self.indexRT)
        self.background = self.createBackground(width + 8, self.minheight + 8)
        self.selection = self.createSelection(width + 8, self.minheight + 8)
        self.updateTextPos()
        self.updateEdges()

    def width(self):
        """
        Returns the width of the shape.
        :rtype: int
        """
        return self.polygon[self.indexBR].x() - self.polygon[self.indexBL].x()

    ####################################################################################################################
    #                                                                                                                  #
    #   GEOMETRY                                                                                                       #
    #                                                                                                                  #
    ####################################################################################################################

    def boundingRect(self):
        """
        Returns the shape bounding rectangle.
        :rtype: QRectF
        """
        return self.selection

    def painterPath(self):
        """
        Returns the current shape as QPainterPath (used for collision detection).
        :rtype: QPainterPath
        """
        path = QPainterPath()
        path.addPolygon(self.polygon)
        return path

    def shape(self):
        """
        Returns the shape of this item as a QPainterPath in local coordinates.
        :rtype: QPainterPath
        """
        path = QPainterPath()
        path.addPolygon(self.polygon)
        return path

    ####################################################################################################################
    #                                                                                                                  #
    #   LABEL SHORTCUTS                                                                                                #
    #                                                                                                                  #
    ####################################################################################################################

    def textPos(self):
        """
        Returns the current label position in item coordinates.
        :rtype: QPointF
        """
        return self.label.pos()

    def text(self):
        """
        Returns the label text.
        :rtype: str
        """
        return self.label.text()

    def setTextPos(self, pos):
        """
        Set the label position.
        :type pos: QPointF
        """
        self.label.setPos(pos)

    def setText(self, text):
        """
        Set the label text.
        :type text: str
        """
        self.label.setText(text)
        self.updateLayout()

    def updateTextPos(self, *args, **kwargs):
        """
        Update the label position.
        """
        self.label.updatePos(*args, **kwargs)

    ####################################################################################################################
    #                                                                                                                  #
    #   DRAWING                                                                                                        #
    #                                                                                                                  #
    ####################################################################################################################

    @classmethod
    def image(cls, **kwargs):
        """
        Returns an image suitable for the palette.
        :rtype: QPixmap
        """
        # INITIALIZATION
        pixmap = QPixmap(kwargs['w'], kwargs['h'])
        pixmap.fill(Qt.transparent)
        painter = QPainter(pixmap)

        polygon = QPolygonF([
            QPointF(+27 - 10, -17),  # 0
            QPointF(-27, -17),  # 1
            QPointF(-27, +17),  # 2
            QPointF(+27, +17),  # 3
            QPointF(+27, -17 + 10),  # 4
            QPointF(+27 - 10, -17),  # 5
        ])

        fold = QPolygonF([
            QPointF(polygon[cls.indexTR].x(), polygon[cls.indexTR].y()),
            QPointF(polygon[cls.indexTR].x(), polygon[cls.indexTR].y() + 10),
            QPointF(polygon[cls.indexRT].x(), polygon[cls.indexRT].y()),
            QPointF(polygon[cls.indexTR].x(), polygon[cls.indexTR].y()),
        ])

        # ITEM SHAPE
        painter.setPen(QPen(QColor(0, 0, 0), 1.0, Qt.SolidLine))
        painter.setBrush(QColor(252, 252, 252))
        painter.translate(kwargs['w'] / 2, kwargs['h'] / 2)
        painter.drawPolygon(polygon)
        painter.drawPolygon(fold)
        # TEXT WITHIN THE SHAPE
        painter.setFont(Font('Arial', 10, Font.Light))
        painter.drawText(polygon.boundingRect(), Qt.AlignCenter,
                         'value\nrestriction')
        return pixmap

    def paint(self, painter, option, widget=None):
        """
        Paint the node in the diagram scene.
        :type painter: QPainter
        :type option: QStyleOptionGraphicsItem
        :type widget: QWidget
        """
        # SET THE RECT THAT NEEDS TO BE REPAINTED
        painter.setClipRect(option.exposedRect)
        # SELECTION AREA
        painter.setPen(self.selectionPen)
        painter.setBrush(self.selectionBrush)
        painter.drawRect(self.selection)
        # SYNTAX VALIDATION
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setPen(self.backgroundPen)
        painter.setBrush(self.backgroundBrush)
        painter.drawRect(self.background)
        # SHAPE
        painter.setPen(self.pen)
        painter.setBrush(self.brush)
        painter.drawPolygon(self.polygon)
        painter.drawPolygon(self.fold)
예제 #9
0
class ValueRestrictionNode(AbstractNode):
    """
    This class implements the 'Value-Restriction' node.
    """
    indexTR = 0
    indexTL = 1
    indexBL = 2
    indexBR = 3
    indexRT = 4
    indexEE = 5

    identities = {Identity.ValueDomain}
    item = Item.ValueRestrictionNode
    minheight = 50
    minwidth = 180

    def __init__(self, width=minwidth, height=minheight, brush=None, **kwargs):
        """
        Initialize the node.
        :type width: int
        :type height: int
        :type brush: QBrush
        """
        super().__init__(**kwargs)
        self.brush = brush or QBrush(QColor(252, 252, 252))
        self.pen = QPen(QColor(0, 0, 0), 1.0, Qt.SolidLine)
        self.polygon = self.createPolygon(self.minwidth, self.minheight)
        self.fold = self.createFold(self.polygon, self.indexTR, self.indexRT)
        self.background = self.createBackground(self.minwidth + 8, self.minheight + 8)
        self.selection = self.createSelection(self.minwidth + 8, self.minheight + 8)
        self.label = Label('xsd:length "32"^^xsd:string', movable=False, editable=False, parent=self)
        self.updateLayout()

    ####################################################################################################################
    #                                                                                                                  #
    #   PROPERTIES                                                                                                     #
    #                                                                                                                  #
    ####################################################################################################################

    @property
    def constrained(self):
        """
        Tells whether the datatype of this restriction is constrained by graph composition.
        :rtype: bool
        """
        f1 = lambda x: x.isItem(Item.InputEdge)
        f2 = lambda x: x.isItem(Item.DatatypeRestrictionNode)
        f3 = lambda x: x.isItem(Item.ValueDomainNode)
        x = first(self.outgoingNodes(filter_on_edges=f1, filter_on_nodes=f2))
        if x:
            return first(x.incomingNodes(filter_on_edges=f1, filter_on_nodes=f3)) is not None
        return False

    @property
    def datatype(self):
        """
        Returns the datatype associated with this node.
        :rtype: XsdDatatype
        """
        match = RE_FACET.match(self.text())
        if match:
            return XsdDatatype.forValue(match.group('datatype'))
        return None

    @property
    def facet(self):
        """
        Returns the facet associated with this node.
        :rtype: Facet
        """
        match = RE_FACET.match(self.text())
        if match:
            return Facet.forValue(match.group('facet'))
        return None

    @property
    def identity(self):
        """
        Returns the identity of the current node.
        :rtype: Identity
        """
        return Identity.ValueDomain

    @identity.setter
    def identity(self, identity):
        """
        Set the identity of the current node.
        :type identity: Identity
        """
        pass

    @property
    def value(self):
        """
        Returns the value of the restriction.
        :rtype: str
        """
        match = RE_FACET.match(self.text())
        if match:
            return match.group('value')
        return ''

    ####################################################################################################################
    #                                                                                                                  #
    #   INTERFACE                                                                                                      #
    #                                                                                                                  #
    ####################################################################################################################

    @staticmethod
    def compose(facet, value, datatype):
        """
        Compose the restriction string.
        :type facet: Facet
        :type value: str
        :type datatype: XsdDatatype
        :return: str
        """
        return '{} "{}"^^{}'.format(facet.value, cutR(cutL(value.strip(), '"'), '"'), datatype.value)

    def copy(self, scene):
        """
        Create a copy of the current item.
        :type scene: DiagramScene
        """
        kwargs = {
            'id': self.id,
            'brush': self.brush,
            'height': self.height(),
            'width': self.width(),
        }
        node = scene.factory.create(item=self.item, scene=scene, **kwargs)
        node.setPos(self.pos())
        node.setText(self.text())
        node.setTextPos(node.mapFromScene(self.mapToScene(self.textPos())))
        return node

    @staticmethod
    def createBackground(width, height):
        """
        Returns the initialized background polygon according to the given width/height.
        :type width: int
        :type height: int
        :rtype: QRectF
        """
        return QRectF(-width / 2, -height / 2, width, height)

    @staticmethod
    def createFold(polygon, indexTR, indexRT):
        """
        Returns the initialized fold polygon.
        :type polygon: QPolygonF
        :type indexTR: int
        :type indexRT: int
        :rtype: QPolygonF
        """
        return QPolygonF([
            QPointF(polygon[indexTR].x(), polygon[indexTR].y()),
            QPointF(polygon[indexTR].x(), polygon[indexTR].y() + 12),
            QPointF(polygon[indexRT].x(), polygon[indexRT].y()),
            QPointF(polygon[indexTR].x(), polygon[indexTR].y()),
        ])
    
    @staticmethod
    def createPolygon(width, height):
        """
        Returns the initialized polygon according to the given width/height.
        :type width: int
        :type height: int
        :rtype: QPolygonF
        """
        return QPolygonF([
            QPointF(+(width / 2) - 12, -(height / 2)),  # 0
            QPointF(-(width / 2), -(height / 2)),       # 1
            QPointF(-(width / 2), +(height / 2)),       # 2
            QPointF(+(width / 2), +(height / 2)),       # 3
            QPointF(+(width / 2), -(height / 2) + 12),  # 4
            QPointF(+(width / 2) - 12, -(height / 2)),  # 5
        ])

    def height(self):
        """
        Returns the height of the shape.
        :rtype: int
        """
        return self.polygon[self.indexBL].y() - self.polygon[self.indexTL].y()

    def updateLayout(self):
        """
        Update current shape rect according to the selected datatype.
        """
        width = max(self.label.width() + 16, self.minwidth)
        self.polygon = self.createPolygon(width, self.minheight)
        self.fold = self.createFold(self.polygon, self.indexTR, self.indexRT)
        self.background = self.createBackground(width + 8, self.minheight + 8)
        self.selection = self.createSelection(width + 8, self.minheight + 8)
        self.updateTextPos()
        self.updateEdges()

    def width(self):
        """
        Returns the width of the shape.
        :rtype: int
        """
        return self.polygon[self.indexBR].x() - self.polygon[self.indexBL].x()

    ####################################################################################################################
    #                                                                                                                  #
    #   GEOMETRY                                                                                                       #
    #                                                                                                                  #
    ####################################################################################################################

    def boundingRect(self):
        """
        Returns the shape bounding rectangle.
        :rtype: QRectF
        """
        return self.selection

    def painterPath(self):
        """
        Returns the current shape as QPainterPath (used for collision detection).
        :rtype: QPainterPath
        """
        path = QPainterPath()
        path.addPolygon(self.polygon)
        return path

    def shape(self):
        """
        Returns the shape of this item as a QPainterPath in local coordinates.
        :rtype: QPainterPath
        """
        path = QPainterPath()
        path.addPolygon(self.polygon)
        return path

    ####################################################################################################################
    #                                                                                                                  #
    #   LABEL SHORTCUTS                                                                                                #
    #                                                                                                                  #
    ####################################################################################################################

    def textPos(self):
        """
        Returns the current label position in item coordinates.
        :rtype: QPointF
        """
        return self.label.pos()

    def text(self):
        """
        Returns the label text.
        :rtype: str
        """
        return self.label.text()

    def setTextPos(self, pos):
        """
        Set the label position.
        :type pos: QPointF
        """
        self.label.setPos(pos)

    def setText(self, text):
        """
        Set the label text.
        :type text: str
        """
        self.label.setText(text)
        self.updateLayout()

    def updateTextPos(self, *args, **kwargs):
        """
        Update the label position.
        """
        self.label.updatePos(*args, **kwargs)

    ####################################################################################################################
    #                                                                                                                  #
    #   DRAWING                                                                                                        #
    #                                                                                                                  #
    ####################################################################################################################

    @classmethod
    def image(cls, **kwargs):
        """
        Returns an image suitable for the palette.
        :rtype: QPixmap
        """
        # INITIALIZATION
        pixmap = QPixmap(kwargs['w'], kwargs['h'])
        pixmap.fill(Qt.transparent)
        painter = QPainter(pixmap)

        polygon = QPolygonF([
            QPointF(+27 - 10, -17),  # 0
            QPointF(-27, -17),       # 1
            QPointF(-27, +17),       # 2
            QPointF(+27, +17),       # 3
            QPointF(+27, -17 + 10),  # 4
            QPointF(+27 - 10, -17),  # 5
        ])
        
        fold = QPolygonF([
            QPointF(polygon[cls.indexTR].x(), polygon[cls.indexTR].y()),
            QPointF(polygon[cls.indexTR].x(), polygon[cls.indexTR].y() + 10),
            QPointF(polygon[cls.indexRT].x(), polygon[cls.indexRT].y()),
            QPointF(polygon[cls.indexTR].x(), polygon[cls.indexTR].y()),
        ])

        # ITEM SHAPE
        painter.setPen(QPen(QColor(0, 0, 0), 1.0, Qt.SolidLine))
        painter.setBrush(QColor(252, 252, 252))
        painter.translate(kwargs['w'] / 2, kwargs['h'] / 2)
        painter.drawPolygon(polygon)
        painter.drawPolygon(fold)
        # TEXT WITHIN THE SHAPE
        painter.setFont(Font('Arial', 10, Font.Light))
        painter.drawText(polygon.boundingRect(), Qt.AlignCenter, 'value\nrestriction')
        return pixmap

    def paint(self, painter, option, widget=None):
        """
        Paint the node in the diagram scene.
        :type painter: QPainter
        :type option: QStyleOptionGraphicsItem
        :type widget: QWidget
        """
        # SET THE RECT THAT NEEDS TO BE REPAINTED
        painter.setClipRect(option.exposedRect)
        # SELECTION AREA
        painter.setPen(self.selectionPen)
        painter.setBrush(self.selectionBrush)
        painter.drawRect(self.selection)
        # SYNTAX VALIDATION
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setPen(self.backgroundPen)
        painter.setBrush(self.backgroundBrush)
        painter.drawRect(self.background)
        # SHAPE
        painter.setPen(self.pen)
        painter.setBrush(self.brush)
        painter.drawPolygon(self.polygon)
        painter.drawPolygon(self.fold)
예제 #10
0
class IndividualNode(AbstractResizableNode):
    """
    This class implements the 'Individual' node.
    """
    indexLT = 0
    indexLB = 1
    indexBL = 2
    indexBR = 3
    indexRB = 4
    indexRT = 5
    indexTR = 6
    indexTL = 7
    indexEE = 8

    identities = {Identity.Instance, Identity.Value}
    item = Item.IndividualNode
    minheight = 60
    minwidth = 60

    def __init__(self, width=minwidth, height=minheight, brush=None, **kwargs):
        """
        Initialize the node.
        :type width: int
        :type height: int
        :type brush: QBrush
        """
        super().__init__(**kwargs)
        w = max(width, self.minwidth)
        h = max(height, self.minheight)
        s = self.handleSize
        self.brush = brush or QBrush(QColor(252, 252, 252))
        self.pen = QPen(QColor(0, 0, 0), 1.0, Qt.SolidLine)
        self.polygon = self.createPolygon(w, h)
        self.background = self.createBackground(w + s, h + s)
        self.selection = self.createSelection(w + s, h + s)
        self.label = Label('instance', parent=self)
        self.label.updatePos()
        self.updateHandles()

    ####################################################################################################################
    #                                                                                                                  #
    #   PROPERTIES                                                                                                     #
    #                                                                                                                  #
    ####################################################################################################################

    @property
    def datatype(self):
        """
        Returns the datatype associated with this node.
        :rtype: XsdDatatype
        """
        match = RE_VALUE.match(self.text())
        if match:
            return XsdDatatype.forValue(match.group('datatype'))
        return None

    @property
    def identity(self):
        """
        Returns the identity of the current node.
        :rtype: Identity
        """
        match = RE_VALUE.match(self.text())
        if match:
            return Identity.Value
        return Identity.Instance

    @property
    def value(self):
        """
        Returns the value value associated with this node.
        :rtype: str
        """
        match = RE_VALUE.match(self.text())
        if match:
            return match.group('value')
        return None

    ####################################################################################################################
    #                                                                                                                  #
    #   INTERFACE                                                                                                      #
    #                                                                                                                  #
    ####################################################################################################################

    @staticmethod
    def composeValue(value, datatype):
        """
        Compose the value string.
        :type value: str
        :type datatype: XsdDatatype
        :return: str
        """
        return '"{}"^^{}'.format(cutR(cutL(value.strip(), '"'), '"'), datatype.value)

    def copy(self, scene):
        """
        Create a copy of the current item.
        :type scene: DiagramScene
        """
        kwargs = {
            'id': self.id,
            'brush': self.brush,
            'height': self.height(),
            'width': self.width(),
        }
        node = scene.factory.create(item=self.item, scene=scene, **kwargs)
        node.setPos(self.pos())
        node.setText(self.text())
        node.setTextPos(node.mapFromScene(self.mapToScene(self.textPos())))
        return node

    @staticmethod
    def createBackground(width, height):
        """
        Returns the initialized background polygon according to the given width/height.
        :type width: int
        :type height: int
        :rtype: QPolygonF
        """
        return QPolygonF([
            QPointF(-(width / 2), -((height / (1 + math.sqrt(2))) / 2)), # 0
            QPointF(-(width / 2), +((height / (1 + math.sqrt(2))) / 2)), # 1
            QPointF(-((width / (1 + math.sqrt(2))) / 2), +(height / 2)), # 2
            QPointF(+((width / (1 + math.sqrt(2))) / 2), +(height / 2)), # 3
            QPointF(+(width / 2), +((height / (1 + math.sqrt(2))) / 2)), # 4
            QPointF(+(width / 2), -((height / (1 + math.sqrt(2))) / 2)), # 5
            QPointF(+((width / (1 + math.sqrt(2))) / 2), -(height / 2)), # 6
            QPointF(-((width / (1 + math.sqrt(2))) / 2), -(height / 2)), # 7
            QPointF(-(width / 2), -((height / (1 + math.sqrt(2))) / 2)), # 8
        ])

    @staticmethod
    def createPolygon(width, height):
        """
        Returns the initialized polygon according to the given width/height.
        :type width: int
        :type height: int
        :rtype: QPolygonF
        """
        return QPolygonF([
            QPointF(-(width / 2), -((height / (1 + math.sqrt(2))) / 2)), # 0
            QPointF(-(width / 2), +((height / (1 + math.sqrt(2))) / 2)), # 1
            QPointF(-((width / (1 + math.sqrt(2))) / 2), +(height / 2)), # 2
            QPointF(+((width / (1 + math.sqrt(2))) / 2), +(height / 2)), # 3
            QPointF(+(width / 2), +((height / (1 + math.sqrt(2))) / 2)), # 4
            QPointF(+(width / 2), -((height / (1 + math.sqrt(2))) / 2)), # 5
            QPointF(+((width / (1 + math.sqrt(2))) / 2), -(height / 2)), # 6
            QPointF(-((width / (1 + math.sqrt(2))) / 2), -(height / 2)), # 7
            QPointF(-(width / 2), -((height / (1 + math.sqrt(2))) / 2)), # 8
        ])
    
    def height(self):
        """
        Returns the height of the shape.
        :rtype: int
        """
        return self.polygon[self.indexTR].y() - self.polygon[self.indexBR].y()

    def interactiveResize(self, mousePos):
        """
        Handle the interactive resize of the shape.
        :type mousePos: QPointF
        """
        scene = self.scene()
        snap = scene.mainwindow.snapToGrid
        size = scene.GridSize
        offset = self.handleSize + self.handleMove
        moved = self.label.moved
        
        R = QRectF(self.boundingRect())
        D = QPointF(0, 0)

        minBoundW = self.minwidth + offset * 2
        minBoundH = self.minheight + offset * 2

        self.prepareGeometryChange()

        if self.mousePressHandle == self.handleTL:

            fromX = self.mousePressBound.left()
            fromY = self.mousePressBound.top()
            toX = fromX + mousePos.x() - self.mousePressPos.x()
            toY = fromY + mousePos.y() - self.mousePressPos.y()
            toX = snapF(toX, size, -offset, snap)
            toY = snapF(toY, size, -offset, snap)
            D.setX(toX - fromX)
            D.setY(toY - fromY)
            R.setLeft(toX)
            R.setTop(toY)

            ## CLAMP SIZE
            if R.width() < minBoundW:
                D.setX(D.x() - minBoundW + R.width())
                R.setLeft(R.left() - minBoundW + R.width())
            if R.height() < minBoundH:
                D.setY(D.y() - minBoundH + R.height())
                R.setTop(R.top() - minBoundH + R.height())

            newSideY = (R.height() - offset * 2) / (1 + math.sqrt(2))
            newSideX = (R.width() - offset * 2) / (1 + math.sqrt(2))
            newLeftRightBottomY = (R.y() + R.height() / 2) + newSideY / 2
            newLeftRightTopY = (R.y() + R.height() / 2) - newSideY / 2
            newTopBottomLeftX = (R.x() + R.width() / 2) - newSideX / 2
            newTopBottomRightX = (R.x() + R.width() / 2) + newSideX / 2

            self.selection.setLeft(R.left())
            self.selection.setTop(R.top())
            
            self.background[self.indexLT] = QPointF(R.left(), newLeftRightTopY)
            self.background[self.indexLB] = QPointF(R.left(), newLeftRightBottomY)
            self.background[self.indexRT] = QPointF(R.right(), newLeftRightTopY)
            self.background[self.indexRB] = QPointF(R.right(), newLeftRightBottomY)
            self.background[self.indexTL] = QPointF(newTopBottomLeftX, R.top())
            self.background[self.indexTR] = QPointF(newTopBottomRightX, R.top())
            self.background[self.indexBL] = QPointF(newTopBottomLeftX, R.bottom())
            self.background[self.indexBR] = QPointF(newTopBottomRightX, R.bottom())
            self.background[self.indexEE] = QPointF(R.left(), newLeftRightTopY)

            self.polygon[self.indexLT] = QPointF(R.left() + offset, newLeftRightTopY)
            self.polygon[self.indexLB] = QPointF(R.left() + offset, newLeftRightBottomY)
            self.polygon[self.indexRT] = QPointF(R.right() - offset, newLeftRightTopY)
            self.polygon[self.indexRB] = QPointF(R.right() - offset, newLeftRightBottomY)
            self.polygon[self.indexTL] = QPointF(newTopBottomLeftX, R.top() + offset)
            self.polygon[self.indexTR] = QPointF(newTopBottomRightX, R.top() + offset)
            self.polygon[self.indexBL] = QPointF(newTopBottomLeftX, R.bottom() - offset)
            self.polygon[self.indexBR] = QPointF(newTopBottomRightX, R.bottom() - offset)
            self.polygon[self.indexEE] = QPointF(R.left() + offset, newLeftRightTopY)

        elif self.mousePressHandle == self.handleTM:

            fromY = self.mousePressBound.top()
            toY = fromY + mousePos.y() - self.mousePressPos.y()
            toY = snapF(toY, size, -offset, snap)
            D.setY(toY - fromY)
            R.setTop(toY)

            ## CLAMP SIZE
            if R.height() < minBoundH:
                D.setY(D.y() - minBoundH + R.height())
                R.setTop(R.top() - minBoundH + R.height())

            newSide = (R.height() - offset * 2) / (1 + math.sqrt(2))
            newLeftRightBottomY = (R.y() + R.height() / 2) + newSide / 2
            newLeftRightTopY = (R.y() + R.height() / 2) - newSide / 2
            
            self.selection.setTop(R.top())
            
            self.background[self.indexTL] = QPointF(self.background[self.indexTL].x(), R.top())
            self.background[self.indexTR] = QPointF(self.background[self.indexTR].x(), R.top())
            self.background[self.indexLB] = QPointF(self.background[self.indexLB].x(), newLeftRightBottomY)
            self.background[self.indexRB] = QPointF(self.background[self.indexRB].x(), newLeftRightBottomY)
            self.background[self.indexLT] = QPointF(self.background[self.indexLT].x(), newLeftRightTopY)
            self.background[self.indexRT] = QPointF(self.background[self.indexRT].x(), newLeftRightTopY)
            self.background[self.indexEE] = QPointF(self.background[self.indexEE].x(), newLeftRightTopY)
            
            self.polygon[self.indexTL] = QPointF(self.polygon[self.indexTL].x(), R.top() + offset)
            self.polygon[self.indexTR] = QPointF(self.polygon[self.indexTR].x(), R.top() + offset)
            self.polygon[self.indexLB] = QPointF(self.polygon[self.indexLB].x(), newLeftRightBottomY)
            self.polygon[self.indexRB] = QPointF(self.polygon[self.indexRB].x(), newLeftRightBottomY)
            self.polygon[self.indexLT] = QPointF(self.polygon[self.indexLT].x(), newLeftRightTopY)
            self.polygon[self.indexRT] = QPointF(self.polygon[self.indexRT].x(), newLeftRightTopY)
            self.polygon[self.indexEE] = QPointF(self.polygon[self.indexEE].x(), newLeftRightTopY)

        elif self.mousePressHandle == self.handleTR:

            fromX = self.mousePressBound.right()
            fromY = self.mousePressBound.top()
            toX = fromX + mousePos.x() - self.mousePressPos.x()
            toY = fromY + mousePos.y() - self.mousePressPos.y()
            toX = snapF(toX, size, +offset, snap)
            toY = snapF(toY, size, -offset, snap)
            D.setX(toX - fromX)
            D.setY(toY - fromY)
            R.setRight(toX)
            R.setTop(toY)

            ## CLAMP SIZE
            if R.width() < minBoundW:
                D.setX(D.x() + minBoundW - R.width())
                R.setRight(R.right() + minBoundW - R.width())
            if R.height() < minBoundH:
                D.setY(D.y() - minBoundH + R.height())
                R.setTop(R.top() - minBoundH + R.height())

            newSideY = (R.height() - offset * 2) / (1 + math.sqrt(2))
            newSideX = (R.width() - offset * 2) / (1 + math.sqrt(2))
            newLeftRightBottomY = (R.y() + R.height() / 2) + newSideY / 2
            newLeftRightTopY = (R.y() + R.height() / 2) - newSideY / 2
            newTopBottomLeftX = (R.x() + R.width() / 2) - newSideX / 2
            newTopBottomRightX = (R.x() + R.width() / 2) + newSideX / 2
            
            self.selection.setRight(R.right())
            self.selection.setTop(R.top())
            
            self.background[self.indexLT] = QPointF(R.left(), newLeftRightTopY)
            self.background[self.indexLB] = QPointF(R.left(), newLeftRightBottomY)
            self.background[self.indexRT] = QPointF(R.right(), newLeftRightTopY)
            self.background[self.indexRB] = QPointF(R.right(), newLeftRightBottomY)
            self.background[self.indexTL] = QPointF(newTopBottomLeftX, R.top())
            self.background[self.indexTR] = QPointF(newTopBottomRightX, R.top())
            self.background[self.indexBL] = QPointF(newTopBottomLeftX, R.bottom())
            self.background[self.indexBR] = QPointF(newTopBottomRightX, R.bottom())
            self.background[self.indexEE] = QPointF(R.left(), newLeftRightTopY)
            
            self.polygon[self.indexLT] = QPointF(R.left() + offset, newLeftRightTopY)
            self.polygon[self.indexLB] = QPointF(R.left() + offset, newLeftRightBottomY)
            self.polygon[self.indexRT] = QPointF(R.right() - offset, newLeftRightTopY)
            self.polygon[self.indexRB] = QPointF(R.right() - offset, newLeftRightBottomY)
            self.polygon[self.indexTL] = QPointF(newTopBottomLeftX, R.top() + offset)
            self.polygon[self.indexTR] = QPointF(newTopBottomRightX, R.top() + offset)
            self.polygon[self.indexBL] = QPointF(newTopBottomLeftX, R.bottom() - offset)
            self.polygon[self.indexBR] = QPointF(newTopBottomRightX, R.bottom() - offset)
            self.polygon[self.indexEE] = QPointF(R.left() + offset, newLeftRightTopY)

        elif self.mousePressHandle == self.handleML:

            fromX = self.mousePressBound.left()
            toX = fromX + mousePos.x() - self.mousePressPos.x()
            toX = snapF(toX, size, -offset, snap)
            D.setX(toX - fromX)
            R.setLeft(toX)

            ## CLAMP SIZE
            if R.width() < minBoundW:
                D.setX(D.x() - minBoundW + R.width())
                R.setLeft(R.left() - minBoundW + R.width())

            newSide = (R.width() - offset * 2) / (1 + math.sqrt(2))
            newTopBottomLeftX = (R.x() + R.width() / 2) - newSide / 2
            newTopBottomRightX = (R.x() + R.width() / 2) + newSide / 2

            self.selection.setLeft(R.left())
            
            self.background[self.indexLT] = QPointF(R.left(), self.background[self.indexLT].y())
            self.background[self.indexLB] = QPointF(R.left(), self.background[self.indexLB].y())
            self.background[self.indexEE] = QPointF(R.left(), self.background[self.indexEE].y())
            self.background[self.indexTL] = QPointF(newTopBottomLeftX, self.background[self.indexTL].y())
            self.background[self.indexTR] = QPointF(newTopBottomRightX, self.background[self.indexTR].y())
            self.background[self.indexBL] = QPointF(newTopBottomLeftX, self.background[self.indexBL].y())
            self.background[self.indexBR] = QPointF(newTopBottomRightX, self.background[self.indexBR].y())
            
            self.polygon[self.indexLT] = QPointF(R.left() + offset, self.polygon[self.indexLT].y())
            self.polygon[self.indexLB] = QPointF(R.left() + offset, self.polygon[self.indexLB].y())
            self.polygon[self.indexEE] = QPointF(R.left() + offset, self.polygon[self.indexEE].y())
            self.polygon[self.indexTL] = QPointF(newTopBottomLeftX, self.polygon[self.indexTL].y())
            self.polygon[self.indexTR] = QPointF(newTopBottomRightX, self.polygon[self.indexTR].y())
            self.polygon[self.indexBL] = QPointF(newTopBottomLeftX, self.polygon[self.indexBL].y())
            self.polygon[self.indexBR] = QPointF(newTopBottomRightX, self.polygon[self.indexBR].y())

        elif self.mousePressHandle == self.handleMR:

            fromX = self.mousePressBound.right()
            toX = fromX + mousePos.x() - self.mousePressPos.x()
            toX = snapF(toX, size, +offset, snap)
            D.setX(toX - fromX)
            R.setRight(toX)

            ## CLAMP SIZE
            if R.width() < minBoundW:
                D.setX(D.x() + minBoundW - R.width())
                R.setRight(R.right() + minBoundW - R.width())

            newSide = (R.width() - offset * 2) / (1 + math.sqrt(2))
            newTopBottomRightX = (R.x() + R.width() / 2) + newSide / 2
            newTopBottomLeftX = (R.x() + R.width() / 2) - newSide / 2
            
            self.selection.setRight(R.right())
            
            self.background[self.indexRT] = QPointF(R.right(), self.background[self.indexRT].y())
            self.background[self.indexRB] = QPointF(R.right(), self.background[self.indexRB].y())
            self.background[self.indexTL] = QPointF(newTopBottomLeftX, self.background[self.indexTL].y())
            self.background[self.indexTR] = QPointF(newTopBottomRightX, self.background[self.indexTR].y())
            self.background[self.indexBL] = QPointF(newTopBottomLeftX, self.background[self.indexBL].y())
            self.background[self.indexBR] = QPointF(newTopBottomRightX, self.background[self.indexBR].y())
            
            self.polygon[self.indexRT] = QPointF(R.right() - offset, self.polygon[self.indexRT].y())
            self.polygon[self.indexRB] = QPointF(R.right() - offset, self.polygon[self.indexRB].y())
            self.polygon[self.indexTL] = QPointF(newTopBottomLeftX, self.polygon[self.indexTL].y())
            self.polygon[self.indexTR] = QPointF(newTopBottomRightX, self.polygon[self.indexTR].y())
            self.polygon[self.indexBL] = QPointF(newTopBottomLeftX, self.polygon[self.indexBL].y())
            self.polygon[self.indexBR] = QPointF(newTopBottomRightX, self.polygon[self.indexBR].y())

        elif self.mousePressHandle == self.handleBL:

            fromX = self.mousePressBound.left()
            fromY = self.mousePressBound.bottom()
            toX = fromX + mousePos.x() - self.mousePressPos.x()
            toY = fromY + mousePos.y() - self.mousePressPos.y()
            toX = snapF(toX, size, -offset, snap)
            toY = snapF(toY, size, +offset, snap)
            D.setX(toX - fromX)
            D.setY(toY - fromY)
            R.setLeft(toX)
            R.setBottom(toY)

            ## CLAMP SIZE
            if R.width() < minBoundW:
                D.setX(D.x() - minBoundW + R.width())
                R.setLeft(R.left() - minBoundW + R.width())
            if R.height() < minBoundH:
                D.setY(D.y() + minBoundH - R.height())
                R.setBottom(R.bottom() + minBoundH - R.height())

            newSideY = (R.height() - offset * 2) / (1 + math.sqrt(2))
            newSideX = (R.width() - offset * 2) / (1 + math.sqrt(2))
            newLeftRightBottomY = (R.y() + R.height() / 2) + newSideY / 2
            newLeftRightTopY = (R.y() + R.height() / 2) - newSideY / 2
            newTopBottomLeftX = (R.x() + R.width() / 2) - newSideX / 2
            newTopBottomRightX = (R.x() + R.width() / 2) + newSideX / 2
            
            self.selection.setLeft(R.left())
            self.selection.setBottom(R.bottom())
            
            self.background[self.indexLT] = QPointF(R.left(), newLeftRightTopY)
            self.background[self.indexLB] = QPointF(R.left(), newLeftRightBottomY)
            self.background[self.indexRT] = QPointF(R.right(), newLeftRightTopY)
            self.background[self.indexRB] = QPointF(R.right(), newLeftRightBottomY)
            self.background[self.indexTL] = QPointF(newTopBottomLeftX, R.top())
            self.background[self.indexTR] = QPointF(newTopBottomRightX, R.top())
            self.background[self.indexBL] = QPointF(newTopBottomLeftX, R.bottom())
            self.background[self.indexBR] = QPointF(newTopBottomRightX, R.bottom())
            self.background[self.indexEE] = QPointF(R.left(), newLeftRightTopY)
            
            self.polygon[self.indexLT] = QPointF(R.left() + offset, newLeftRightTopY)
            self.polygon[self.indexLB] = QPointF(R.left() + offset, newLeftRightBottomY)
            self.polygon[self.indexRT] = QPointF(R.right() - offset, newLeftRightTopY)
            self.polygon[self.indexRB] = QPointF(R.right() - offset, newLeftRightBottomY)
            self.polygon[self.indexTL] = QPointF(newTopBottomLeftX, R.top() + offset)
            self.polygon[self.indexTR] = QPointF(newTopBottomRightX, R.top() + offset)
            self.polygon[self.indexBL] = QPointF(newTopBottomLeftX, R.bottom() - offset)
            self.polygon[self.indexBR] = QPointF(newTopBottomRightX, R.bottom() - offset)
            self.polygon[self.indexEE] = QPointF(R.left() + offset, newLeftRightTopY)

        elif self.mousePressHandle == self.handleBM:

            fromY = self.mousePressBound.bottom()
            toY = fromY + mousePos.y() - self.mousePressPos.y()
            toY = snapF(toY, size, +offset, snap)
            D.setY(toY - fromY)
            R.setBottom(toY)

            ## CLAMP SIZE
            if R.height() < minBoundH:
                D.setY(D.y() + minBoundH - R.height())
                R.setBottom(R.bottom() + minBoundH - R.height())

            newSide = (R.height() - offset * 2) / (1 + math.sqrt(2))
            newLeftRightTopY = (R.y() + R.height() / 2) - newSide / 2
            newLeftRightBottomY = (R.y() + R.height() / 2) + newSide / 2
            
            self.selection.setBottom(R.bottom())
            
            self.background[self.indexBL] = QPointF(self.background[self.indexBL].x(), R.bottom())
            self.background[self.indexBR] = QPointF(self.background[self.indexBR].x(), R.bottom())
            self.background[self.indexLB] = QPointF(self.background[self.indexLB].x(), newLeftRightBottomY)
            self.background[self.indexRB] = QPointF(self.background[self.indexRB].x(), newLeftRightBottomY)
            self.background[self.indexLT] = QPointF(self.background[self.indexLT].x(), newLeftRightTopY)
            self.background[self.indexRT] = QPointF(self.background[self.indexRT].x(), newLeftRightTopY)
            self.background[self.indexEE] = QPointF(self.background[self.indexEE].x(), newLeftRightTopY)
            
            self.polygon[self.indexBL] = QPointF(self.polygon[self.indexBL].x(), R.bottom() - offset)
            self.polygon[self.indexBR] = QPointF(self.polygon[self.indexBR].x(), R.bottom() - offset)
            self.polygon[self.indexLB] = QPointF(self.polygon[self.indexLB].x(), newLeftRightBottomY)
            self.polygon[self.indexRB] = QPointF(self.polygon[self.indexRB].x(), newLeftRightBottomY)
            self.polygon[self.indexLT] = QPointF(self.polygon[self.indexLT].x(), newLeftRightTopY)
            self.polygon[self.indexRT] = QPointF(self.polygon[self.indexRT].x(), newLeftRightTopY)
            self.polygon[self.indexEE] = QPointF(self.polygon[self.indexEE].x(), newLeftRightTopY)

        elif self.mousePressHandle == self.handleBR:

            fromX = self.mousePressBound.right()
            fromY = self.mousePressBound.bottom()
            toX = fromX + mousePos.x() - self.mousePressPos.x()
            toY = fromY + mousePos.y() - self.mousePressPos.y()
            toX = snapF(toX, size, +offset, snap)
            toY = snapF(toY, size, +offset, snap)
            D.setX(toX - fromX)
            D.setY(toY - fromY)
            R.setRight(toX)
            R.setBottom(toY)

            ## CLAMP SIZE
            if R.width() < minBoundW:
                D.setX(D.x() + minBoundW - R.width())
                R.setRight(R.right() + minBoundW - R.width())
            if R.height() < minBoundH:
                D.setY(D.y() + minBoundH - R.height())
                R.setBottom(R.bottom() + minBoundH - R.height())

            newSideY = (R.height() - offset * 2) / (1 + math.sqrt(2))
            newSideX = (R.width() - offset * 2) / (1 + math.sqrt(2))
            newLeftRightBottomY = (R.y() + R.height() / 2) + newSideY / 2
            newLeftRightTopY = (R.y() + R.height() / 2) - newSideY / 2
            newTopBottomLeftX = (R.x() + R.width() / 2) - newSideX / 2
            newTopBottomRightX = (R.x() + R.width() / 2) + newSideX / 2

            self.selection.setRight(R.right())
            self.selection.setBottom(R.bottom())

            self.background[self.indexLT] = QPointF(R.left(), newLeftRightTopY)
            self.background[self.indexLB] = QPointF(R.left(), newLeftRightBottomY)
            self.background[self.indexRT] = QPointF(R.right(), newLeftRightTopY)
            self.background[self.indexRB] = QPointF(R.right(), newLeftRightBottomY)
            self.background[self.indexTL] = QPointF(newTopBottomLeftX, R.top())
            self.background[self.indexTR] = QPointF(newTopBottomRightX, R.top())
            self.background[self.indexBL] = QPointF(newTopBottomLeftX, R.bottom())
            self.background[self.indexBR] = QPointF(newTopBottomRightX, R.bottom())
            self.background[self.indexEE] = QPointF(R.left(), newLeftRightTopY)
            
            self.polygon[self.indexLT] = QPointF(R.left() + offset, newLeftRightTopY)
            self.polygon[self.indexLB] = QPointF(R.left() + offset, newLeftRightBottomY)
            self.polygon[self.indexRT] = QPointF(R.right() - offset, newLeftRightTopY)
            self.polygon[self.indexRB] = QPointF(R.right() - offset, newLeftRightBottomY)
            self.polygon[self.indexTL] = QPointF(newTopBottomLeftX, R.top() + offset)
            self.polygon[self.indexTR] = QPointF(newTopBottomRightX, R.top() + offset)
            self.polygon[self.indexBL] = QPointF(newTopBottomLeftX, R.bottom() - offset)
            self.polygon[self.indexBR] = QPointF(newTopBottomRightX, R.bottom() - offset)
            self.polygon[self.indexEE] = QPointF(R.left() + offset, newLeftRightTopY)

        self.updateHandles()
        self.updateTextPos(moved=moved)
        self.updateAnchors(self.mousePressData, D)

    def width(self):
        """
        Returns the width of the shape.
        :rtype: int
        """
        return self.polygon[self.indexRT].x() - self.polygon[self.indexLT].x()

    ####################################################################################################################
    #                                                                                                                  #
    #   GEOMETRY                                                                                                       #
    #                                                                                                                  #
    ####################################################################################################################

    def boundingRect(self):
        """
        Returns the shape bounding rectangle.
        :rtype: QRectF
        """
        return self.selection

    def painterPath(self):
        """
        Returns the current shape as QPainterPath (used for collision detection).
        :rtype: QPainterPath
        """
        path = QPainterPath()
        path.addPolygon(self.polygon)
        return path

    def shape(self):
        """
        Returns the shape of this item as a QPainterPath in local coordinates.
        :rtype: QPainterPath
        """
        path = QPainterPath()
        path.addPolygon(self.polygon)
        for shape in self.handleBound:
            path.addEllipse(shape)
        return path

    ####################################################################################################################
    #                                                                                                                  #
    #   LABEL SHORTCUTS                                                                                                #
    #                                                                                                                  #
    ####################################################################################################################

    def textPos(self):
        """
        Returns the current label position in item coordinates.
        :rtype: QPointF
        """
        return self.label.pos()

    def text(self):
        """
        Returns the label text.
        :rtype: str
        """
        return self.label.text()

    def setTextPos(self, pos):
        """
        Set the label position.
        :type pos: QPointF
        """
        self.label.setPos(pos)

    def setText(self, text):
        """
        Set the label text: will additionally block label editing if a literal is being.
        :type text: str
        """
        self.label.editable = RE_VALUE.match(text) is None
        self.label.setText(text)

    def updateTextPos(self, *args, **kwargs):
        """
        Update the label position.
        """
        self.label.updatePos(*args, **kwargs)

    ####################################################################################################################
    #                                                                                                                  #
    #   DRAWING                                                                                                        #
    #                                                                                                                  #
    ####################################################################################################################

    @classmethod
    def image(cls, **kwargs):
        """
        Returns an image suitable for the palette.
        :rtype: QPixmap
        """
        # INITIALIZATION
        pixmap = QPixmap(kwargs['w'], kwargs['h'])
        pixmap.fill(Qt.transparent)
        painter = QPainter(pixmap)
        polygon = cls.createPolygon(40, 40)
        # ITEM SHAPE
        painter.setPen(QPen(QColor(0, 0, 0), 1.0, Qt.SolidLine))
        painter.setBrush(QColor(252, 252, 252))
        painter.translate(kwargs['w'] / 2, kwargs['h'] / 2)
        painter.drawPolygon(polygon)
        # TEXT WITHIN THE SHAPE
        painter.setFont(Font('Arial', 9, Font.Light))
        painter.drawText(-16, 4, 'instance')
        return pixmap

    def paint(self, painter, option, widget=None):
        """
        Paint the node in the diagram scene.
        :type painter: QPainter
        :type option: QStyleOptionGraphicsItem
        :type widget: QWidget
        """
        # SET THE RECT THAT NEEDS TO BE REPAINTED
        painter.setClipRect(option.exposedRect)
        # SELECTION AREA
        painter.setPen(self.selectionPen)
        painter.setBrush(self.selectionBrush)
        painter.drawRect(self.selection)
        # SYNTAX VALIDATION
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setPen(self.backgroundPen)
        painter.setBrush(self.backgroundBrush)
        painter.drawPolygon(self.background)
        # ITEM SHAPE
        painter.setPen(self.pen)
        painter.setBrush(self.brush)
        painter.drawPolygon(self.polygon)
        # RESIZE HANDLES
        painter.setRenderHint(QPainter.Antialiasing)
        for i in range(self.handleNum):
            painter.setBrush(self.handleBrush[i])
            painter.setPen(self.handlePen[i])
            painter.drawEllipse(self.handleBound[i])