示例#1
0
    def createNetPath(self, brushColor: str, painterPath: QPainterPath,
                      isOccupyPathItem: bool):
        """ Create a QGraphicsPathItem for a network path
            
            Args:
                brushColor (str)            : The color for filling the rectangles
                painterPath (QPainterPath)  : The path to be inserted to the item
                isOccupyPathItem (bool)     : Whether the path is occupied or unoccupied path
        """
        # Generate the path item if not created
        if isOccupyPathItem:
            if self.occupiedPathItem is None:
                self.occupiedPathItem = QGraphicsPathItem(self)
            pathItem = self.occupiedPathItem
        else:
            if self.unoccupiedPathItem is None:
                self.unoccupiedPathItem = QGraphicsPathItem(self)
            pathItem = self.unoccupiedPathItem
        if pathItem is None:
            pathItem = QGraphicsPathItem(self)

        # Set the item parameters
        pathItem.setPath(painterPath)
        pathItem.setPen(
            QPen(QColor(self.netColor), self.netThickness, style=Qt.SolidLine))
        pathItem.setBrush(QBrush(QColor(brushColor)))
        pathItem.setZValue(0)
示例#2
0
class createDiscretionWindow(view.descritiondefinition_ui.Ui_discretionWindow, QDialog):
    def __init__(self, isCrack, he, parent=None):
        super(createDiscretionWindow, self).__init__(parent)
        self.highlight = QGraphicsPathItem()
        self.highlight.setPath(he)
        pen = QPen(Qt.red, 2)
        self.highlight.setPen(pen)
        self.highlight.setZValue(1)
        self.parent().scene.addItem(self.highlight)
        self.setupUi(self)
        self.move(1450, -1)
        self.isCrack = isCrack
        self.lineEdit_number_of_elements.setFocus(True)
        self.lineEdit_number_of_elements.textChanged.connect(self.change_number_of_discontinous_elements)
        self.radioButton_2.toggled.connect(self.setDiscontinousBoxState)
        if self.isCrack == True:
            self.checkBox_isCrack.setChecked(True)
            self.radioButton_2.setChecked(True)
            self.checkBox_isCrack.setEnabled(False)
            self.groupBox_type_of_discretization.setEnabled(False)

    def setDiscontinousBoxState(self):
        newState = not self.groupBox_discontinous_discretion.isEnabled()
        self.groupBox_discontinous_discretion.setEnabled(newState)

    def change_number_of_discontinous_elements(self):
        if self.radioButton_2.isChecked():
            if self.lineEdit_number_of_elements.text() != "":
                if int(self.lineEdit_number_of_elements.text()) > self.verticalLayout_2.count():
                    for i in range(int(self.lineEdit_number_of_elements.text()) - self.verticalLayout_2.count()):
                        lineEdit = QLineEdit()
                        validator = QDoubleValidator()
                        validator.setNotation(0)
                        validator.setRange(0.1, 0.9, decimals=3)
                        lineEdit.setValidator(validator)
                        self.verticalLayout_2.addWidget(lineEdit)
                else:
                    for i in range(self.verticalLayout_2.count()-1, int(self.lineEdit_number_of_elements.text())-1, -1):
                        itemToDelete = self.verticalLayout_2.itemAt(i).widget()
                        self.verticalLayout_2.removeWidget(itemToDelete)
                        itemToDelete.deleteLater()
            else:
                pass

    @classmethod
    def getDiscretion(cls, he, isCrack=False, parent=None):
        dialog = cls(isCrack, he, parent)
        dialog.exec_()
        if dialog.radioButton.isChecked():
            dialog.parent().scene.removeItem(dialog.highlight)
            if dialog.lineEdit_number_of_elements.text() != "":
                return int(dialog.lineEdit_number_of_elements.text()), False
            else:
                return 1, False
        else:
            spaces = [0]
            for i in range(dialog.verticalLayout_2.count()):
                spaces.append(float(dialog.verticalLayout_2.itemAt(i).widget().text()) + spaces[-1])
            dialog.parent().scene.removeItem(dialog.highlight)
            return spaces, True
示例#3
0
    def createNetPath(self, brushColor: str, painterPath: QPainterPath, isOccupyPathItem: bool):
        """ Create a QGraphicsPathItem for a network path
            
            Args:
                brushColor (str)            : The color for filling the rectangles
                painterPath (QPainterPath)  : The path to be inserted to the item
                isOccupyPathItem (bool)     : Whether the path is occupied or unoccupied path
        """
        # Generate the path item if not created
        if isOccupyPathItem:
            if self.occupiedPathItem is None:
                self.occupiedPathItem = QGraphicsPathItem(self)
            pathItem = self.occupiedPathItem
        else:
            if self.unoccupiedPathItem is None:
                self.unoccupiedPathItem = QGraphicsPathItem(self)
            pathItem = self.unoccupiedPathItem
        if pathItem is None:
            pathItem = QGraphicsPathItem(self)

        # Set the item parameters
        pathItem.setPath(painterPath)
        pathItem.setPen(QPen(QColor(self.netColor), self.netThickness, style=Qt.SolidLine))
        pathItem.setBrush(QBrush(QColor(brushColor)))
        pathItem.setZValue(0)
示例#4
0
 def updateConnector(self,
                     item: QGraphicsPathItem,
                     p1: QPointF,
                     p2: QPointF,
                     select: bool = True):
     path = QPainterPath(p1)
     path.quadTo(p1 + QPointF(-15, 0), (p1 + p2) * 0.5)
     path.quadTo(p2 + QPointF(15, 0), p2)
     if item is None:
         item = self.nodesScene.addPath(path)
     else:
         item.setPath(path)
     item.setZValue(-1)
     if select:
         item.setFlag(QGraphicsItem.ItemIsSelectable)
     return item
示例#5
0
def FillScene(aScene, aProcessGraphics):	
		
	maxRow = 0
	maxCol = 0
	for graphic in aProcessGraphics:
		maxRow = max(maxRow, graphic.row)
		maxCol = max(maxCol, graphic.col)
	maxRow = maxRow + 1
	maxCol = maxCol + 1
	
	colWidth = 400
	rowHeigth = 360
	border = 50

	sceneWidth = colWidth * maxCol + border * 2
	sceneHeight = rowHeigth * maxRow + border * 2
	aScene.setSceneRect(QRectF(0, 0, sceneWidth, sceneHeight))
	aScene.clear()

	## Set process positions
	for graphic in aProcessGraphics:
		x = 50 + sceneWidth - (graphic.col + 1) * colWidth
		y = 50 + graphic.row * rowHeigth
		graphic.setPos(QPointF(x, y))
		aScene.addItem(graphic)

		
	## Add lines
	for graphic in aProcessGraphics:
		for inp in graphic.inputs:
			for child in inp.children:
				controlOffset = 100
				start = child.GetScenePos()
				control1 = start + QPointF(controlOffset, 0)
				end = inp.GetScenePos()
				control2 = end + QPointF(-controlOffset, 0)

				path = QPainterPath()
				path.moveTo(start)
				path.cubicTo(control1, control2, end)

				line = QGraphicsPathItem(path)
				line.setZValue(-1000)
				aScene.addItem(line)
示例#6
0
class PathMaker(QWidget):
    def __init__(self, parent):
        super().__init__()

        self.canvas = parent
        self.scene = parent.scene
        self.view = parent.view
        self.dots = parent.dots
        self.chooser = None  ## placeholder for popup_widget

        self.initThis()

        self.sliders = self.dots.sliderpanel  ## for toggling key menu
        self.sideWays = SideWays(self)  ## extends pathMaker

        self.direct = {
            'F': self.sideWays.openFiles,
            'C': self.sideWays.centerPath,
            'P': self.pathChooser,
            '{': self.sideWays.flipPath,
            '}': self.sideWays.flopPath,
        }

        self.setMouseTracking(True)
        self.view.viewport().installEventFilter(self)

### --------------------------------------------------------

    def initThis(self):
        self.pts = []
        self.npts = 0  ## counter used by addNewPathPts
        self.key = ""

        self.color = "DODGERBLUE"
        self.openPathFile = ''
        self.pointTag = ""
        self.tag = ''

        self.pathSet = False
        self.newPathSet = False
        self.pathChooserSet = False

        self.ball = None
        self.path = None
        self.newPath = None

        self.pathBall = None
        self.tagGroup = None

        self.pathTestSet = False
        self.wayPtsSet = False  ## appear as tags

### ---------------------- key handler ---------------------

    @pyqtSlot(str)
    def pathKeys(self, key):
        self.key = key
        if self.key == 'D':  ## always
            self.delete()
        elif self.key == '/':
            self.sideWays.changePathColor()
        elif self.newPathSet and self.key == 'cmd':  ## note
            self.closeNewPath()
        elif key in NotPathSetKeys:
            if self.key == 'R':
                self.sideWays.reversePath()
            elif self.key == 'S':
                self.sideWays.savePath()
            elif self.key == 'T':
                self.sideWays.pathTest()
            elif self.key == 'W':
                self.sideWays.addWayPtTags()
            elif self.key == 'N':
                if not self.pathSet and not self.wayPtsSet:
                    if not self.newPathSet:
                        self.addNewPath()
                    else:
                        self.newPathOff()  ## changed your mind
                        self.delete()
        ## not waypts and not new path
        elif not self.wayPtsSet and not self.newPathSet:
            if key in self.direct:
                self.direct[key]()  ## OK..
            elif key in MoveKeys:
                self.sideWays.movePath(key)
            elif key in ScaleRotateKeys:
                self.sideWays.scaleRotate(key)
        ##  waypts only
        elif self.wayPtsSet and key in WayPtsKeys:
            if self.key == '!':
                self.sideWays.halfPath()
            elif self.key == 'V':
                self.togglePointItems()
            elif self.key in ('<', '>'):
                self.sideWays.shiftWayPts(key)

### ----------------- event filter..not used  ---------------

    ''' PathMaker mouse events for drawing a path '''
    def eventFilter(self, source, e):
        if self.canvas.pathMakerOn and self.newPathSet:
            if e.type() == QEvent.MouseButtonPress:
                self.npts = 0
                self.addNewPathPts(QPoint(e.pos()))
            elif e.type() == QEvent.MouseMove:
                self.addNewPathPts(QPoint(e.pos()))
            elif e.type() == QEvent.MouseButtonRelease:
                self.addNewPathPts(QPoint(e.pos()))
                self.updateNewPath()
            return False
        return QWidget.eventFilter(self, source, e)

### --------------------------------------------------------

    def initPathMaker(self):  ## from docks button
        if self.scene.items() and not self.canvas.pathMakerOn:
            MsgBox("Clear Scene First to run PathMaker")
            return
        elif self.canvas.pathMakerOn:
            self.pathMakerOff()
        else:
            self.canvas.pathMakerOn = True
            self.scene.clear()
            self.initThis()
            if not self.sliders.pathMenuSet:
                self.sliders.toggleMenu()
            self.turnGreen()
            # QTimer.singleShot(200, self.pathChooser)  ## optional

    def turnGreen(self):
        self.dots.btnPathMaker.setStyleSheet("background-color: LIGHTGREEN")

    def delete(self):
        self.stopPathTest()
        self.removePointItems()
        self.removePath()
        self.removeWayPtTags()
        self.removeNewPath()
        self.pathChooserOff()
        self.scene.clear()
        self.initThis()

    def pathMakerOff(self):
        self.delete()
        self.canvas.pathMakerOn = False
        if self.sliders.pathMenuSet:
            self.sliders.toggleMenu()
        self.dots.btnPathMaker.setStyleSheet("background-color: white")

    def pathChooser(self):
        if not self.pathChooserSet and not self.newPathSet:
            self.chooser = DoodleMaker(self)
            self.chooser.move(600, 200)
            self.chooser.show()
            self.pathChooserSet = True
        else:
            self.pathChooserOff()

    def pathChooserOff(self):
        self.chooser = None
        self.pathChooserSet = False

### -------------------- new path -------------------------

    def addNewPathPts(self, pt):  ## called by dropCanvas eventfilter
        if self.npts == 0:
            self.pts.append(pt)
        self.npts += 1
        if self.npts % 3 == 0:
            self.pts.append(pt)
            self.updateNewPath()

    def addNewPath(self):
        self.dots.btnPathMaker.setStyleSheet(
            "background-color: rgb(215,165,255)")
        self.initNewPath(True)

    def initNewPath(self, bool):
        self.newPath = None
        self.newPathSet = bool
        self.pts = []
        self.npts = 0

    def closeNewPath(self):  ## applies only to newPath
        self.removeNewPath()
        self.addPath()  ## add the completed path
        self.turnGreen()

    def newPathOff(self):
        if self.newPathSet:
            if self.newPath:
                self.scene.removeItem(self.newPath)
            self.initNewPath(False)
            self.turnGreen()

    def updateNewPath(self):
        if self.pts:  ## list of points
            self.removeNewPath()  ## clean up just in case
            self.newPath = QGraphicsPathItem(self.sideWays.setPaintPath())
            self.newPath.setPen(QPen(QColor(self.color), 3, Qt.DashDotLine))
            self.newPath.setZValue(common['pathZ'])
            self.scene.addItem(self.newPath)  ## only one - no group needed
            self.newPathSet = True

    def removeNewPath(self):  ## keep self.pts
        if self.newPath:
            self.scene.removeItem(self.newPath)
            self.newPathSet = False
            self.newPath = None

### -------------------- path stuff ------------------------

    def addPath(self):
        self.removePath()
        self.path = QGraphicsPathItem(self.sideWays.setPaintPath(True))
        self.path.setPen(QPen(QColor(self.color), 3, Qt.DashDotLine))
        self.path.setZValue(common['pathZ'])
        self.scene.addItem(self.path)
        self.pathSet = True

    def removePath(self):
        if self.path:
            self.scene.removeItem(self.path)
            self.pathSet = False
            self.path = None

### --------------- pointItems and tags --------------------

    def togglePointItems(self):
        if self.pointItemsSet():
            self.removePointItems()
        else:
            self.addPointItems()
        QTimer.singleShot(200, self.redrawPathsAndTags)

    def redrawPathsAndTags(self):
        self.removeWayPtTags()
        self.removePath()
        self.addPath()
        self.sideWays.addWayPtTags()

    def findTop(self):
        for itm in self.scene.items():
            return itm.zValue()
        return 0

    def printZ(self):  ## alternate
        print(self.scene.items()[0].zValue())

    def addPointItems(self):
        idx = 0
        add = self.findTop() + 10
        for pt in self.pts:
            self.scene.addItem(PointItem(self, pt, idx, add))
            idx += 1

    def removePointItems(self):
        for pt in self.scene.items():
            if pt.type == 'pt':
                self.scene.removeItem(pt)

    def pointItemsSet(self):
        for itm in self.scene.items():
            if itm.type == 'pt':
                return True
        return False

    def insertPointItem(self, pointItem):
        idx, pt = pointItem.idx + 1, pointItem.pt
        if idx == len(self.pts): idx = 0
        pt1 = self.pts[idx]
        pt1 = QPointF(pt1.x() - pt.x(), pt1.y() - pt.y())
        pt1 = pt + QPointF(pt1) * .5
        self.pts.insert(idx, pt1)
        self.redrawPoints()

    def delPointItem(self, pointItem):
        self.pts.pop(pointItem.idx)
        self.redrawPoints()

    def redrawPoints(self, bool=True):  ## pointItems points
        self.removePointItems()
        self.removeWayPtTags()
        self.removePath()
        self.addPath()
        self.sideWays.addWayPtTags()
        if bool: self.addPointItems()

    def addPointTag(self, pnt):  ## single tag
        pct = (pnt.idx / len(self.pts)) * 100
        tag = self.sideWays.makePtsTag(pnt.pt, pnt.idx, pct)
        self.pointTag = TagIt('points', tag, QColor("YELLOW"))
        p = QPointF(0, -20)
        self.pointTag.setPos(pnt.pt + p)
        self.pointTag.setZValue(self.findTop() + 5)
        self.scene.addItem(self.pointTag)

    def addWayPtTag(self, tag, pt):
        self.tag = TagIt('pathMaker', tag, QColor("TOMATO"))
        self.tag.setPos(pt)
        self.tag.setZValue(common["tagZ"] + 5)
        self.tagGroup.addToGroup(self.tag)

    def removePointTag(self):  ## single tag
        if self.pointTag:
            self.scene.removeItem(self.pointTag)
            self.pointTag = ''

    def addWayPtTagsGroup(self):
        self.tagGroup = QGraphicsItemGroup()
        self.scene.addItem(self.tagGroup)
        self.tagGroup.setZValue(common["tagZ"] + 5)

    def removeWayPtTags(self):
        if self.tagGroup or self.wayPtsSet:
            self.scene.removeItem(self.tagGroup)
            self.tagGroup = None
            self.wayPtsSet = False

    def startPathTest(self):
        self.scene.addItem(self.ball)
        self.pathBall.start()
        self.pathTestSet = True

    def stopPathTest(self):
        if self.pathTestSet:
            self.pathBall.stop()
            self.scene.removeItem(self.ball)
            self.pathBall = None
            self.pathTestSet = False
示例#7
0
class CharItem(QGraphicsRectItem):
    """ This item represents character item

        The purpose of the class is to draw a character, create a matrix
        of rectangles and resolve in which rectangles the character passes

        The class allow the following operations 
        -#  Drawing a character using the mouse events:
            -#  Start by the mouse press event
            -#  Continues by the mouse move event
            -#  The character is stored in QGraphicsPathItem
        -#  Transform the character to occupy the whole item's space
        -#  Set operation : resolving the Occupied matrix which tell on which rectangle the character
            passes
        -#  Reset operation : reverse the character transform so it is possible to continue
            drawing the character
        -#  Save operation : To a QDataStream
        -#  Load operation : From a QDataStream

        The graphical view of the class is composed from:
        -#  This class which inherits from QGraphicsRectItem and holds :
        -#  A QGraphicsPathItem : representing the character
        -#  A QGraphicsPathItem : representing the occupied rectangles
        -#  A QGraphicsPathItem : representing the unoccupied rectangles
    """
    def __init__(self, rect: QRectF, pos: QPointF, viewIndex: int = -1):
        """ CharItem constructor

            Args:
                rect    (QRectF)    : The rectangle that the character should fill
                pos     (QPointF)   : The position of the item within the parent
                viewIndex (int)     : The index of the item in case it is presented in multi character presentation
        """
        super(CharItem, self).__init__(rect)
        self.setAcceptedMouseButtons(Qt.LeftButton)
        self.setPresentationPrms()
        self.occupied = [[False for idx in range(self.netCols)]
                         for idx in range(self.netRows)]
        self.charPath = None
        self.wasSetted = False
        self.occupiedPathItem = None
        self.unoccupiedPathItem = None
        self.dirty = False
        self.viewIndex = viewIndex
        self.filename = ""
        self.boundaries = rect
        self.dx = 1
        self.dy = 1
        self.posInParent = pos
        self.setPos(self.posInParent)

    def setPresentationPrms(self):
        """ Setting the presentation prms

            The reason the there is a duplicate set of presentation parameters is
            that it allows changing the presentation parameters for one character
            (like in the select option
        """

        self.netColor = netColor
        self.netThickness = netThickness
        self.occupyColor = occupyColor
        self.unOccupyColor = unOccupyColor
        self.shapeColor = shapeColor
        self.shapeLineThickness = shapeLineThickness
        self.selectedOccupiedColor = selectedOccupiedColor
        self.selectedShapeColor = selectedShapeColor
        self.netRows = netRows
        self.netCols = netCols

    def setNetBoxDimensions(self, rect: QRectF):
        """ Set net box dimensions
        
            The net box is the rectangle that compose the network
            drawn to show the occupy matrix
        """
        self.netRectHeight = rect.height() / self.netRows
        self.netRectWidth = rect.width() / self.netCols
        self.left = rect.left()
        self.top = rect.top()

    def netRect(self, row_idx: int, col_idx: int) -> QRectF:
        """ Set net rect
        
            The net box is the rectangle that compose the network
            drawn to show the occupy matrix

            Args:
                row_idx  (int)   : The row of the network rectangle
                col_idx  (int)   : The col of the network rectangle

            Returns:
                QRectF  : The rectangle
        """
        return QRectF(self.left + col_idx * self.netRectWidth,
                      self.top + row_idx * self.netRectHeight,
                      self.netRectWidth, self.netRectHeight)

    def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent):
        """ Mouse move event : continue draw a line

            This event is activated when the mouse is pressed and moves

            The methods draws the line in 2 conditions:
            -# The item is not part of multi character presentation
            -# A character path was initiated (using the mouse press event)

            Args:
               event (QGraphicsSceneMouseEvent) : the event description
        """
        if self.viewIndex == -1:
            if self.charPath is not None:
                point = event.scenePos()
                path = self.charPath.path()
                path.lineTo(point)
                self.charPath.setPath(path)
                self.update()

    def mousePressEvent(self, event: QGraphicsSceneMouseEvent):
        """ Mouse Press Event : Start a new line / Select the character

            If the character is part of multi character presentation 
                activate the character selection
            If the character is in single character presentation
                Start a new line in the character

            Args:
               event (QGraphicsSceneMouseEvent) : the event description
        """
        if self.viewIndex == -1:
            self.startLine(event)
        else:
            self.setSelected()

    def startLine(self, event: QGraphicsSceneMouseEvent):
        """ Start drawing a line

            When the mouse button is pressed and we are in single character
            dialog this method is activated to start drowning a line in the
            character

            Args:
               event (QGraphicsSceneMouseEvent) : the event description
        """
        # There are 2 modes for the presentation:
        # Original mode where the character is it's original size
        # After setting mode when the set was done and the character fullfill all
        # the item's space
        # Drawing can be done only in original mode
        if self.wasSetted:
            QMessageBox.critical(
                None, "Char identifier window",
                "The shape was already setted use revert setting")
            return

        # If this is the first start of a line - generate the QPainterPath and QGraphicsPathItem
        if self.charPath is None:
            self.initCharPath()

        # Move to the mouse position
        point = event.scenePos()
        path = self.charPath.path()
        path.moveTo(point)
        self.charPath.setPath(path)
        self.dirty = True

    def initCharPath(self):
        """ Init the item that holds the character

            There is one path item that holds the character
            This method is activated by start line if the char item 
            was not created to create the new and only one
        """
        self.dirty = True
        self.charPath = QGraphicsPathItem(self)
        self.charPath.setPen(
            QPen(QColor(self.shapeColor), self.shapeLineThickness))
        self.charPath.setZValue(1)
        self.charPath.originalPos = self.charPath.pos()
        self.charPath.setPath(QPainterPath())

    def setSelected(self):
        """ Set the item a selected item

            This method is activated when the mouse button is presses
            and the item is part of multi character presentation
        """

        # Set the colors of the item
        self.occupiedPathItem.setBrush(
            QBrush(QColor(self.selectedOccupiedColor)))
        self.charPath.setPen(
            QPen(QColor(self.selectedShapeColor), self.shapeLineThickness))
        self.update()

        # Report to the parent item about the selection
        self.parentItem().setSelected(self.viewIndex)

    def resetSelected(self):
        """ Set the colors of the item to not selected
        """

        self.occupiedPathItem.setBrush(QBrush(QColor(self.occupyColor)))
        self.charPath.setPen(
            QPen(QColor(self.shapeColor), self.shapeLineThickness))
        self.update()

    def set(self):
        """ Calculate the occupied matrix and present the results

            This method does the following:
            -# Fill the occupied matrix
            -# Generate the occupied and unoccupied pathes items
            -# Transform the char path to fit to the item's boundaries
        """
        # If there is no shape drawn - return
        if self.charPath is None:
            QMessageBox.critical(None, "Char identifier window",
                                 "There is no shape drawn")
            return

        # If the item is in setted mode - return
        if self.wasSetted:
            QMessageBox.critical(
                None, "Char identifier window",
                "The shape was already setted use revert setting")
            return

        # fill the occupied matrix with the data before the scaling
        self.fillOccupied()
        self.setNetBoxDimensions(self.boundingRect())
        self.createNetPaths()

        # update the transform - change the dimensions and location
        # only on the first time
        self.transformCharPath()

        self.wasSetted = True
        # update the presentation
        self.update()

    def revertTransform(self):
        """ Change from Setted mode to drawing mode

            The drawing mode is the mode where the character can be drawn
            -#  Restore the original size of the character (Reset the transform of the char item)
            -#  Restor the char path item's position to the original one (saved when created)
            -#  Empty the occupiedPath and the unoccupiedPath
        """
        # If there is no character drawn - return
        if self.charPath is None:
            QMessageBox.critical(None, "Char identifier window",
                                 "There is no shape drawn")
            return

        # If the item is already in drawing mode - return
        if not self.wasSetted:
            QMessageBox.critical(None, "Char identifier window",
                                 "The shape was not setted use set button")
            return

        # The char path item
        transform = self.charPath.transform()
        transform.reset()

        # The self.dx and self.dy are the scale parameters created when the item
        # begins and they are the scale parameters that transform it to the boundaries
        # given by the parent item
        transform.scale(self.dx, self.dy)
        self.charPath.setTransform(transform)
        self.charPath.setPos(self.charPath.originalPos)

        # Empty the network pathes
        self.occupiedPathItem.setPath(QPainterPath())
        self.unoccupiedPathItem.setPath(QPainterPath())
        self.wasSetted = False

    def transformCharPath(self):
        """ Transform char path when the item is setted

            This method does the following
            -#  scale the char path to the size of the item
            -#  calculate the new position of the char path so that it will
                be placed at the top left corner of the item
        """
        dx = self.boundingRect().width() / self.charPath.boundingRect().width()
        dy = self.boundingRect().height() / self.charPath.boundingRect(
        ).height()
        transform = self.charPath.transform()
        transform.reset()
        transform.scale(dx, dy)
        self.charPath.setTransform(transform)

        # Move the shape to the origin
        moveX = -(self.charPath.boundingRect().left() -
                  self.boundingRect().left()) * dx
        moveY = -(self.charPath.boundingRect().top() -
                  self.boundingRect().top()) * dy
        self.charPath.setX(self.charPath.x() + moveX)
        self.charPath.setY(self.charPath.y() + moveY)

    def fillOccupied(self):
        """ Fill the occupied matrix

            The algorithm of filling the occupied matrix is 
            -#  Scanning the char path
            -#  For each point decide on where row and column of the net
            -#  Set the occupies matrix for this column and row to True
        """

        for idx in range(100):
            point = self.charPath.path().pointAtPercent(idx / 100.)
            row_idx, col_idx = self.calcRowCol(point)
            self.occupied[row_idx][col_idx] = True

    def calcRowCol(self, point: QPointF):
        """ Calculate the network row and column that a point is int
            calc the row and column indexes of a point

            The following is the algorithm:
            1.  Find the distance between the point and the left (or top)
            2.  Divide the distance with the width of path to find the relative position
            3.  Multipile this relative position with the number of rows/cols
            4.  Convert the result to int to find the indexes
            5.  If the index is the number of row/col reduce the index
                (This is for the case the the point is on the boundary and in this
                case the relative position is 1 which will cause the indexes to
                be the number of rows/cols - out of the matrix indexes)

            Args:
                point (QPointF) : The point to resolve

            Returns:
                int : The network row that the point is in
                int : The network column that the point is in  

        """
        partialX = (point.x() - self.charPath.boundingRect().left()
                    ) / self.charPath.boundingRect().width()
        partialY = (point.y() - self.charPath.boundingRect().top()
                    ) / self.charPath.boundingRect().height()
        col_idx = int(partialX * self.netCols)
        row_idx = int(partialY * self.netRows)
        if row_idx == self.netRows:
            row_idx -= 1
        if col_idx == self.netCols:
            col_idx -= 1
        return row_idx, col_idx

    def createNetPaths(self):
        """ Create the network pathes

            This method creates 2 network pathes items one for holding
            the occupied rectangles and one to hold the unoccupied rectangles
        """
        # Generate 2 QPainterPath
        occupiedPath = QPainterPath()
        unoccupiedPath = QPainterPath()

        # For each entry in occupied matrix :
        # Add a rectangle to the appropriate path according the entry value
        for row_idx in range(self.netRows):
            for col_idx in range(self.netCols):
                if self.occupied[row_idx][col_idx]:
                    occupiedPath.addRect(self.netRect(row_idx, col_idx))
                else:
                    unoccupiedPath.addRect(self.netRect(row_idx, col_idx))

        # Create the QGraphicsPathItems that will hold the path
        self.createNetPath(self.occupyColor, occupiedPath, True)
        self.createNetPath(self.unOccupyColor, unoccupiedPath, False)

    def createNetPath(self, brushColor: str, painterPath: QPainterPath,
                      isOccupyPathItem: bool):
        """ Create a QGraphicsPathItem for a network path
            
            Args:
                brushColor (str)            : The color for filling the rectangles
                painterPath (QPainterPath)  : The path to be inserted to the item
                isOccupyPathItem (bool)     : Whether the path is occupied or unoccupied path
        """
        # Generate the path item if not created
        if isOccupyPathItem:
            if self.occupiedPathItem is None:
                self.occupiedPathItem = QGraphicsPathItem(self)
            pathItem = self.occupiedPathItem
        else:
            if self.unoccupiedPathItem is None:
                self.unoccupiedPathItem = QGraphicsPathItem(self)
            pathItem = self.unoccupiedPathItem
        if pathItem is None:
            pathItem = QGraphicsPathItem(self)

        # Set the item parameters
        pathItem.setPath(painterPath)
        pathItem.setPen(
            QPen(QColor(self.netColor), self.netThickness, style=Qt.SolidLine))
        pathItem.setBrush(QBrush(QColor(brushColor)))
        pathItem.setZValue(0)

    def save(self, stream: QDataStream, filename: str):
        """ Save the item to QDataStream
            
            Args:
                stream  (QDataStream)   : The data stream to write the item to
                filename (str)          : The filename (for documenting purposes)
        """

        # The item position
        stream << self.pos()

        # The dimensions
        stream << self.rect()

        # The presentation parameters
        stream.writeQString(self.netColor)
        stream.writeQString(self.occupyColor)
        stream.writeQString(self.unOccupyColor)
        stream.writeQString(self.shapeColor)
        stream.writeInt16(self.shapeLineThickness)
        stream.writeInt16(self.netRows)
        stream.writeInt16(self.netRows)

        # The items paths
        stream << self.charPath.path()
        self.dirty = False
        self.filename = filename

    def load(self, stream, filename):
        """ Loads the item from QDataStream
            
            Args:
                stream  (QDataStream)   : The data stream to read the item from
                filename (str)          : The filename (for documenting purposes)
        """

        # read the pos
        pos = QPointF()
        stream >> pos
        self.setPos(pos)

        # read the dimensions
        rect = QRectF()
        stream >> rect
        self.setRect(rect)

        # The presentation parameters
        self.netColor = stream.readQString()
        self.occupyColor = stream.readQString()
        self.unOccupyColor = stream.readQString()
        self.shapeColor = stream.readQString()
        self.shapeLineThickness = stream.readInt16()
        self.netRows = stream.readInt16()
        self.netRows = stream.readInt16()

        # read the paths
        self.initCharPath()
        path = self.charPath.path()
        stream >> path
        self.charPath.setPath(path)

        # Fit the item to the boundaries and position given by the item's parent
        self.fitToBoundaries()

        # The presentation of the item is in setted mode so we activate the set method
        self.wasSetted = False
        self.set()

        self.dirty = False
        self.filename = filename

    def fitToBoundaries(self):
        """ Fit the item to the boundaries and position given by it's parent

            This method was made to support the change of the character
            boundaries and that the char can be presented in different
            boundaries and position
        """
        self.setPos(self.posInParent)
        self.dx = self.boundaries.width() / self.rect().width()
        self.dy = self.boundaries.height() / self.rect().height()
        transform = self.transform()
        transform.scale(self.dx, self.dy)
        self.setTransform(transform)
示例#8
0
class MapData(dict):
    def __init__(self, zone=None):
        super().__init__()
        self.zone = zone
        self.raw = {'lines': [], 'poi': [], 'grid': []}
        self.geometry = None  # MapGeometry
        self.players = {}
        self.spawns = []
        self.way_point = None
        self.grid = None

        if self.zone is not None:
            self._load()

    def _load(self):
        # Get list of all map files for current zone
        map_file_name = MapData.get_zone_dict()[self.zone.strip().lower()]
        extensions = ['.txt', '_1.txt', '_2.txt', '_3.txt', '_4.txt', '_5.txt']
        maps = [
            os.path.join(MAP_FILES_LOCATION, m)
            for m in [(map_file_name + e) for e in extensions]
            if os.path.exists(os.path.join(MAP_FILES_LOCATION, m))
        ]

        all_x, all_y, all_z = [], [], []

        # TODO: Remove the references to raw
        # Create Lines and Points
        for map_file in maps:
            with open(map_file, 'r') as f:
                for line in f.readlines():
                    line_type = line.lower()[0:1]
                    data = [value.strip() for value in line[1:].split(',')]
                    if line_type == 'l':  # line
                        x1, y1, z1, x2, y2, z2 = list(map(float, data[0:6]))
                        self.raw['lines'].append(
                            MapLine(x1=x1,
                                    y1=y1,
                                    z1=z1,
                                    x2=x2,
                                    y2=y2,
                                    z2=z2,
                                    color=self.color_transform(
                                        QColor(int(data[6]), int(data[7]),
                                               int(data[8])))))
                        all_x.extend((x1, x2))
                        all_y.extend((y1, y2))
                        all_z.append(min(z1, z2))
                        # if abs(z1 - z2) < 2:
                        # if z1 == z2:
                        # all_z.extend((z1, z2))

                    elif line_type == 'p':  # point
                        x, y, z = map(float, data[0:3])
                        self.raw['poi'].append(
                            MapPoint(x=x,
                                     y=y,
                                     z=z,
                                     size=int(data[6]),
                                     text=str(data[7]),
                                     color=self.color_transform(
                                         QColor(int(data[3]), int(data[4]),
                                                int(data[5])))))

        # Create Grid Lines
        lowest_x, highest_x, lowest_y, highest_y, lowest_z, highest_z = min(
            all_x), max(all_x), min(all_y), max(all_y), min(all_z), max(all_z)

        left, right = int(math.floor(lowest_x / 1000) * 1000), int(
            math.ceil(highest_x / 1000) * 1000)
        top, bottom = int(math.floor(lowest_y / 1000) * 1000), int(
            math.ceil(highest_y / 1000) * 1000)

        for number in range(left, right + 1000, 1000):
            self.raw['grid'].append(
                MapLine(x1=number,
                        x2=number,
                        y1=top,
                        y2=bottom,
                        z1=0,
                        z2=0,
                        color=QColor(255, 255, 255, 25)))

        for number in range(top, bottom + 1000, 1000):
            self.raw['grid'].append(
                MapLine(y1=number,
                        y2=number,
                        x1=left,
                        x2=right,
                        z1=0,
                        z2=0,
                        color=QColor(255, 255, 255, 25)))

        self.grid = QGraphicsPathItem()
        line_path = QPainterPath()
        for line in self.raw['grid']:
            line_path.moveTo(line.x1, line.y1)
            line_path.lineTo(line.x2, line.y2)
        self.grid.setPath(line_path)
        self.grid.setPen(
            QPen(line.color, config.data['maps']['grid_line_width']))
        self.grid.setZValue(0)

        # Get z levels
        counter = Counter(all_z)

        # bunch together zgroups based on peaks with floor being low point before rise
        z_groups = []
        last_value = None
        first_run = True
        for z in sorted(counter.items(), key=lambda x: x[0]):
            if last_value is None:
                last_value = z
                continue
            if (abs(last_value[0] - z[0]) < 20) or z[1] < 8:
                last_value = (last_value[0], last_value[1] + z[1])
            else:
                if first_run:
                    first_run = False
                    if last_value[1] < 40 or abs(last_value[0] - z[0]) < 18:
                        last_value = z
                        continue
                z_groups.append(last_value[0])
                last_value = z

        # get last iteration
        if last_value[1] > 50:
            z_groups.append(last_value[0])

        self._z_groups = z_groups

        # Create QGraphicsPathItem for lines seperately to retain colors
        temp_dict = {}
        for l in self.raw['lines']:
            lz = min(l.z1, l.z2)
            lz = self.get_closest_z_group(lz)
            if not temp_dict.get(lz, None):
                temp_dict[lz] = {'paths': {}}
            lc = l.color.getRgb()
            if not temp_dict[lz]['paths'].get(lc, None):
                path_item = QGraphicsPathItem()
                path_item.setPen(
                    QPen(l.color, config.data['maps']['line_width']))
                temp_dict[lz]['paths'][lc] = path_item
            path = temp_dict[lz]['paths'][lc].path()
            path.moveTo(l.x1, l.y1)
            path.lineTo(l.x2, l.y2)
            temp_dict[lz]['paths'][lc].setPath(path)

        # Group QGraphicsPathItems into QGraphicsItemGroups and update self
        for z in temp_dict.keys():
            item_group = QGraphicsItemGroup()
            for (_, path) in temp_dict[z]['paths'].items():
                item_group.addToGroup(path)
            self[z] = {'paths': None, 'poi': []}
            self[z]['paths'] = item_group

        # Create Points of Interest
        for p in self.raw['poi']:
            z = self.get_closest_z_group(p.z)
            self[z]['poi'].append(PointOfInterest(location=p))

        self.geometry = MapGeometry(
            lowest_x=lowest_x,
            highest_x=highest_x,
            lowest_y=lowest_y,
            highest_y=highest_y,
            lowest_z=lowest_z,
            highest_z=highest_z,
            center_x=int(highest_x - (highest_x - lowest_x) / 2),
            center_y=int(highest_y - (highest_y - lowest_y) / 2),
            width=int(highest_x - lowest_x),
            height=int(highest_y - lowest_y),
            z_groups=z_groups)

    def get_closest_z_group(self, z):
        closest = min(self._z_groups, key=lambda x: abs(x - z))
        if z < closest:
            lower_index = self._z_groups.index(closest) - 1
            if lower_index > -1:
                closest = self._z_groups[lower_index]
        return closest

    @staticmethod
    def get_zone_dict():
        # Load Map Pairs from map_keys.ini
        zone_dict = {}
        with open(MAP_KEY_FILE, 'r') as file:
            for line in file.readlines():
                values = line.split('=')
                zone_dict[values[0].strip()] = values[1].strip()
        return zone_dict

    @staticmethod
    def color_transform(color):
        lightness = color.lightness()
        if lightness == 0:
            return QColor(255, 255, 255)
        elif (color.red == color.green == color.blue):
            return QColor(255, 255, 255)
        elif lightness < 150:
            return color.lighter(150)
        return color
示例#9
0
class CharItem(QGraphicsRectItem):
    """ This item represents character item

        The purpose of the class is to draw a character, create a matrix
        of rectangles and resolve in which rectangles the character passes

        The class allow the following operations 
        -#  Drawing a character using the mouse events:
            -#  Start by the mouse press event
            -#  Continues by the mouse move event
            -#  The character is stored in QGraphicsPathItem
        -#  Transform the character to occupy the whole item's space
        -#  Set operation : resolving the Occupied matrix which tell on which rectangle the character
            passes
        -#  Reset operation : reverse the character transform so it is possible to continue
            drawing the character
        -#  Save operation : To a QDataStream
        -#  Load operation : From a QDataStream

        The graphical view of the class is composed from:
        -#  This class which inherits from QGraphicsRectItem and holds :
        -#  A QGraphicsPathItem : representing the character
        -#  A QGraphicsPathItem : representing the occupied rectangles
        -#  A QGraphicsPathItem : representing the unoccupied rectangles
    """

    def __init__(self, rect: QRectF, pos: QPointF, viewIndex: int=-1):
        """ CharItem constructor

            Args:
                rect    (QRectF)    : The rectangle that the character should fill
                pos     (QPointF)   : The position of the item within the parent
                viewIndex (int)     : The index of the item in case it is presented in multi character presentation
        """
        super(CharItem, self).__init__(rect)
        self.setAcceptedMouseButtons(Qt.LeftButton)
        self.setPresentationPrms()
        self.occupied = [[False for idx in range(self.netCols)] for idx in range(self.netRows)]
        self.charPath = None
        self.wasSetted = False
        self.occupiedPathItem = None
        self.unoccupiedPathItem = None
        self.dirty = False
        self.viewIndex = viewIndex
        self.filename = ""
        self.boundaries = rect
        self.dx = 1
        self.dy = 1
        self.posInParent = pos
        self.setPos(self.posInParent)

    def setPresentationPrms(self):
        """ Setting the presentation prms

            The reason the there is a duplicate set of presentation parameters is
            that it allows changing the presentation parameters for one character
            (like in the select option
        """

        self.netColor = netColor
        self.netThickness = netThickness
        self.occupyColor = occupyColor
        self.unOccupyColor = unOccupyColor
        self.shapeColor = shapeColor
        self.shapeLineThickness = shapeLineThickness
        self.selectedOccupiedColor = selectedOccupiedColor
        self.selectedShapeColor = selectedShapeColor
        self.netRows = netRows
        self.netCols = netCols

    def setNetBoxDimensions(self, rect: QRectF):
        """ Set net box dimensions
        
            The net box is the rectangle that compose the network
            drawn to show the occupy matrix
        """
        self.netRectHeight = rect.height() / self.netRows
        self.netRectWidth = rect.width() / self.netCols
        self.left = rect.left()
        self.top = rect.top()

    def netRect(self, row_idx: int, col_idx: int) -> QRectF:
        """ Set net rect
        
            The net box is the rectangle that compose the network
            drawn to show the occupy matrix

            Args:
                row_idx  (int)   : The row of the network rectangle
                col_idx  (int)   : The col of the network rectangle

            Returns:
                QRectF  : The rectangle
        """
        return QRectF(self.left + col_idx * self.netRectWidth, self.top + row_idx * self.netRectHeight, self.netRectWidth,
                      self.netRectHeight)

    def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent):
        """ Mouse move event : continue draw a line

            This event is activated when the mouse is pressed and moves

            The methods draws the line in 2 conditions:
            -# The item is not part of multi character presentation
            -# A character path was initiated (using the mouse press event)

            Args:
               event (QGraphicsSceneMouseEvent) : the event description
        """
        if self.viewIndex == -1:
            if self.charPath is not None:
                point = event.scenePos()
                path = self.charPath.path()
                path.lineTo(point)
                self.charPath.setPath(path)
                self.update()

    def mousePressEvent(self, event: QGraphicsSceneMouseEvent):
        """ Mouse Press Event : Start a new line / Select the character

            If the character is part of multi character presentation 
                activate the character selection
            If the character is in single character presentation
                Start a new line in the character

            Args:
               event (QGraphicsSceneMouseEvent) : the event description
        """
        if self.viewIndex == -1:
            self.startLine(event)
        else:
            self.setSelected()

    def startLine(self, event: QGraphicsSceneMouseEvent):
        """ Start drawing a line

            When the mouse button is pressed and we are in single character
            dialog this method is activated to start drowning a line in the
            character

            Args:
               event (QGraphicsSceneMouseEvent) : the event description
        """
        # There are 2 modes for the presentation:
        # Original mode where the character is it's original size
        # After setting mode when the set was done and the character fullfill all
        # the item's space
        # Drawing can be done only in original mode
        if self.wasSetted:
            QMessageBox.critical(None, "Char identifier window", "The shape was already setted use revert setting")
            return

        # If this is the first start of a line - generate the QPainterPath and QGraphicsPathItem
        if self.charPath is None:
            self.initCharPath()

        # Move to the mouse position
        point = event.scenePos()
        path = self.charPath.path()
        path.moveTo(point)
        self.charPath.setPath(path)
        self.dirty = True

    def initCharPath(self):
        """ Init the item that holds the character

            There is one path item that holds the character
            This method is activated by start line if the char item 
            was not created to create the new and only one
        """
        self.dirty = True
        self.charPath = QGraphicsPathItem(self)
        self.charPath.setPen(QPen(QColor(self.shapeColor), self.shapeLineThickness))
        self.charPath.setZValue(1)
        self.charPath.originalPos = self.charPath.pos()
        self.charPath.setPath(QPainterPath())

    def setSelected(self):
        """ Set the item a selected item

            This method is activated when the mouse button is presses
            and the item is part of multi character presentation
        """

        # Set the colors of the item
        self.occupiedPathItem.setBrush(QBrush(QColor(self.selectedOccupiedColor)))
        self.charPath.setPen(QPen(QColor(self.selectedShapeColor), self.shapeLineThickness))
        self.update()

        # Report to the parent item about the selection
        self.parentItem().setSelected(self.viewIndex)

    def resetSelected(self):
        """ Set the colors of the item to not selected
        """

        self.occupiedPathItem.setBrush(QBrush(QColor(self.occupyColor)))
        self.charPath.setPen(QPen(QColor(self.shapeColor), self.shapeLineThickness))
        self.update()

    def set(self):
        """ Calculate the occupied matrix and present the results

            This method does the following:
            -# Fill the occupied matrix
            -# Generate the occupied and unoccupied pathes items
            -# Transform the char path to fit to the item's boundaries
        """
        # If there is no shape drawn - return
        if self.charPath is None:
            QMessageBox.critical(None, "Char identifier window", "There is no shape drawn")
            return

        # If the item is in setted mode - return
        if self.wasSetted:
            QMessageBox.critical(None, "Char identifier window", "The shape was already setted use revert setting")
            return

        # fill the occupied matrix with the data before the scaling
        self.fillOccupied()
        self.setNetBoxDimensions(self.boundingRect())
        self.createNetPaths()

        # update the transform - change the dimensions and location
        # only on the first time
        self.transformCharPath()

        self.wasSetted = True
        # update the presentation
        self.update()

    def revertTransform(self):
        """ Change from Setted mode to drawing mode

            The drawing mode is the mode where the character can be drawn
            -#  Restore the original size of the character (Reset the transform of the char item)
            -#  Restor the char path item's position to the original one (saved when created)
            -#  Empty the occupiedPath and the unoccupiedPath
        """
        # If there is no character drawn - return
        if self.charPath is None:
            QMessageBox.critical(None, "Char identifier window", "There is no shape drawn")
            return

        # If the item is already in drawing mode - return
        if not self.wasSetted:
            QMessageBox.critical(None, "Char identifier window", "The shape was not setted use set button")
            return

        # The char path item
        transform = self.charPath.transform()
        transform.reset()

        # The self.dx and self.dy are the scale parameters created when the item
        # begins and they are the scale parameters that transform it to the boundaries
        # given by the parent item
        transform.scale(self.dx, self.dy)
        self.charPath.setTransform(transform)
        self.charPath.setPos(self.charPath.originalPos)

        # Empty the network pathes
        self.occupiedPathItem.setPath(QPainterPath())
        self.unoccupiedPathItem.setPath(QPainterPath())
        self.wasSetted = False

    def transformCharPath(self):
        """ Transform char path when the item is setted

            This method does the following
            -#  scale the char path to the size of the item
            -#  calculate the new position of the char path so that it will
                be placed at the top left corner of the item
        """
        dx = self.boundingRect().width() / self.charPath.boundingRect().width()
        dy = self.boundingRect().height() / self.charPath.boundingRect().height()
        transform = self.charPath.transform()
        transform.reset()
        transform.scale(dx, dy)
        self.charPath.setTransform(transform)

        # Move the shape to the origin
        moveX = -(self.charPath.boundingRect().left() - self.boundingRect().left()) * dx
        moveY = -(self.charPath.boundingRect().top() - self.boundingRect().top()) * dy
        self.charPath.setX(self.charPath.x() + moveX)
        self.charPath.setY(self.charPath.y() + moveY)

    def fillOccupied(self):
        """ Fill the occupied matrix

            The algorithm of filling the occupied matrix is 
            -#  Scanning the char path
            -#  For each point decide on where row and column of the net
            -#  Set the occupies matrix for this column and row to True
        """

        for idx in range(100):
            point = self.charPath.path().pointAtPercent(idx / 100.)
            row_idx, col_idx = self.calcRowCol(point)
            self.occupied[row_idx][col_idx] = True

    def calcRowCol(self, point: QPointF):
        """ Calculate the network row and column that a point is int
            calc the row and column indexes of a point

            The following is the algorithm:
            1.  Find the distance between the point and the left (or top)
            2.  Divide the distance with the width of path to find the relative position
            3.  Multipile this relative position with the number of rows/cols
            4.  Convert the result to int to find the indexes
            5.  If the index is the number of row/col reduce the index
                (This is for the case the the point is on the boundary and in this
                case the relative position is 1 which will cause the indexes to
                be the number of rows/cols - out of the matrix indexes)

            Args:
                point (QPointF) : The point to resolve

            Returns:
                int : The network row that the point is in
                int : The network column that the point is in  

        """
        partialX = (point.x() - self.charPath.boundingRect().left()) / self.charPath.boundingRect().width()
        partialY = (point.y() - self.charPath.boundingRect().top()) / self.charPath.boundingRect().height()
        col_idx = int(partialX * self.netCols)
        row_idx = int(partialY * self.netRows)
        if row_idx == self.netRows:
            row_idx -= 1
        if col_idx == self.netCols:
            col_idx -= 1
        return row_idx, col_idx

    def createNetPaths(self):
        """ Create the network pathes

            This method creates 2 network pathes items one for holding
            the occupied rectangles and one to hold the unoccupied rectangles
        """
        # Generate 2 QPainterPath
        occupiedPath = QPainterPath()
        unoccupiedPath = QPainterPath()

        # For each entry in occupied matrix :
        # Add a rectangle to the appropriate path according the entry value
        for row_idx in range(self.netRows):
            for col_idx in range(self.netCols):
                if self.occupied[row_idx][col_idx]:
                    occupiedPath.addRect(self.netRect(row_idx, col_idx))
                else:
                    unoccupiedPath.addRect(self.netRect(row_idx, col_idx))

        # Create the QGraphicsPathItems that will hold the path
        self.createNetPath(self.occupyColor, occupiedPath, True)
        self.createNetPath(self.unOccupyColor, unoccupiedPath, False)

    def createNetPath(self, brushColor: str, painterPath: QPainterPath, isOccupyPathItem: bool):
        """ Create a QGraphicsPathItem for a network path
            
            Args:
                brushColor (str)            : The color for filling the rectangles
                painterPath (QPainterPath)  : The path to be inserted to the item
                isOccupyPathItem (bool)     : Whether the path is occupied or unoccupied path
        """
        # Generate the path item if not created
        if isOccupyPathItem:
            if self.occupiedPathItem is None:
                self.occupiedPathItem = QGraphicsPathItem(self)
            pathItem = self.occupiedPathItem
        else:
            if self.unoccupiedPathItem is None:
                self.unoccupiedPathItem = QGraphicsPathItem(self)
            pathItem = self.unoccupiedPathItem
        if pathItem is None:
            pathItem = QGraphicsPathItem(self)

        # Set the item parameters
        pathItem.setPath(painterPath)
        pathItem.setPen(QPen(QColor(self.netColor), self.netThickness, style=Qt.SolidLine))
        pathItem.setBrush(QBrush(QColor(brushColor)))
        pathItem.setZValue(0)

    def save(self, stream: QDataStream, filename: str):
        """ Save the item to QDataStream
            
            Args:
                stream  (QDataStream)   : The data stream to write the item to
                filename (str)          : The filename (for documenting purposes)
        """

        # The item position
        stream << self.pos()

        # The dimensions
        stream << self.rect()

        # The presentation parameters
        stream.writeQString(self.netColor)
        stream.writeQString(self.occupyColor)
        stream.writeQString(self.unOccupyColor)
        stream.writeQString(self.shapeColor)
        stream.writeInt16(self.shapeLineThickness)
        stream.writeInt16(self.netRows)
        stream.writeInt16(self.netRows)

        # The items paths
        stream << self.charPath.path()
        self.dirty = False
        self.filename = filename

    def load(self, stream, filename):
        """ Loads the item from QDataStream
            
            Args:
                stream  (QDataStream)   : The data stream to read the item from
                filename (str)          : The filename (for documenting purposes)
        """

        # read the pos
        pos = QPointF()
        stream >> pos
        self.setPos(pos)

        # read the dimensions
        rect = QRectF()
        stream >> rect
        self.setRect(rect)

        # The presentation parameters
        self.netColor = stream.readQString()
        self.occupyColor = stream.readQString()
        self.unOccupyColor = stream.readQString()
        self.shapeColor = stream.readQString()
        self.shapeLineThickness = stream.readInt16()
        self.netRows = stream.readInt16()
        self.netRows = stream.readInt16()

        # read the paths
        self.initCharPath()
        path = self.charPath.path()
        stream >> path
        self.charPath.setPath(path)

        # Fit the item to the boundaries and position given by the item's parent
        self.fitToBoundaries()

        # The presentation of the item is in setted mode so we activate the set method
        self.wasSetted = False
        self.set()

        self.dirty = False
        self.filename = filename

    def fitToBoundaries(self):
        """ Fit the item to the boundaries and position given by it's parent

            This method was made to support the change of the character
            boundaries and that the char can be presented in different
            boundaries and position
        """
        self.setPos(self.posInParent)
        self.dx = self.boundaries.width() / self.rect().width()
        self.dy = self.boundaries.height() / self.rect().height()
        transform = self.transform()
        transform.scale(self.dx, self.dy)
        self.setTransform(transform)
示例#10
0
class NodeItem(QGraphicsObject):
    """
    An widget node item in the canvas.
    """

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

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

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

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

    #: Span of the anchor in degrees
    ANCHOR_SPAN_ANGLE = 90

    #: Z value of the item
    Z_VALUE = 100

    def __init__(self, widget_description=None, parent=None, **kwargs):
        self.__boundingRect = None

        QGraphicsObject.__init__(self, parent, **kwargs)

        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
        self.setFlag(QGraphicsItem.ItemHasNoContents, True)
        self.setFlag(QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.setFlag(QGraphicsItem.ItemIsFocusable, True)

        # central body shape item
        self.shapeItem = None

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

        # title text item
        self.captionTextItem = None

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

        # background when selected
        self.backgroundItem = None

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

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

        self.__anchorLayout = None
        self.__animationEnabled = False

        self.setZValue(self.Z_VALUE)
        self.setupGraphics()
        self.setWidgetDescription(widget_description)

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

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

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

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

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

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

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

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

        # Title caption item
        self.captionTextItem = NameTextItem(self)

        self.captionTextItem.setPlainText("")
        self.captionTextItem.setPos(0, 33)


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

            return item


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


        ################################
        # PyQt 5.10 crashes because of this call (2)
        #self.backgroundItem = QGraphicsPathItem(self)
        self.backgroundItem = QGraphicsPathItem(None)
        ################################

        backgroundrect = QPainterPath()
        backgroundrect.addRoundedRect(anchor_rect.adjusted(-4, -2, 4, 2),
                                      5, 5, mode=Qt.AbsoluteSize)
        self.backgroundItem.setPen(QPen(Qt.NoPen))
        self.backgroundItem.setBrush(QPalette().brush(QPalette.Highlight))
        self.backgroundItem.setOpacity(0.5)
        self.backgroundItem.setPath(backgroundrect)
        self.backgroundItem.setZValue(-10)
        self.backgroundItem.setVisible(self.isSelected())

        self.prepareGeometryChange()
        self.__boundingRect = None


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    def setStatusMessage(self, message):
        """
        Set the node status message text.

        This text is displayed below the node's title.

        """
        if self.__statusMessage != message:
            self.__statusMessage = six.text_type(message)
            self.__updateTitleText()

    def statusMessage(self):
        return self.__statusMessage

    def setStateMessage(self, message):
        """
        Set a state message to display over the item.

        Parameters
        ----------
        message : UserMessage
            Message to display. `message.severity` is used to determine
            the icon and `message.contents` is used as a tool tip.

        """
        # TODO: Group messages by message_id not by severity
        # and deprecate set[Error|Warning|Error]Message
        if message.severity == UserMessage.Info:
            self.setInfoMessage(message.contents)
        elif message.severity == UserMessage.Warning:
            self.setWarningMessage(message.contents)
        elif message.severity == UserMessage.Error:
            self.setErrorMessage(message.contents)

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

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

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

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

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

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

        return anchor

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

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

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

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

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

        return anchor

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

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

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

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

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

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

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

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

    def __updateTitleText(self):
        """
        Update the title text item.
        """
        text = ['<div align="center">%s' % escape(self.title())]

        status_text = []

        progress_included = False
        if self.__statusMessage:
            msg = escape(self.__statusMessage)
            format_fields = dict(parse_format_fields(msg))
            if "progress" in format_fields and len(format_fields) == 1:
                # Insert progress into the status text format string.
                spec, _ = format_fields["progress"]
                if spec != None:
                    progress_included = True
                    progress_str = "{0:.0f}%".format(self.progress())
                    status_text.append(msg.format(progress=progress_str))
            else:
                status_text.append(msg)

        if self.progress() >= 0 and not progress_included:
            status_text.append("%i%%" % int(self.progress()))

        if status_text:
            text += ["<br/>",
                     '<span style="font-style: italic">',
                     "<br/>".join(status_text),
                     "</span>"]
        text += ["</div>"]
        text = "".join(text)

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

    def __updateMessages(self):
        """
        Update message items (position, visibility and tool tips).
        """
        items = [self.errorItem, self.warningItem, self.infoItem]

        messages = [self.__error, self.__warning, self.__info]
        for message, item in zip(messages, items):
            item.setVisible(bool(message))
            item.setToolTip(message or "")

        shown = [item for item in items if item.isVisible()]
        count = len(shown)
        if count:
            spacing = 3
            rects = [item.boundingRect() for item in shown]
            width = sum(rect.width() for rect in rects)
            width += spacing * max(0, count - 1)
            height = max(rect.height() for rect in rects)
            origin = self.shapeItem.boundingRect().top() - spacing - height
            origin = QPointF(-width / 2, origin)
            for item, rect in zip(shown, rects):
                item.setPos(origin)
                origin = origin + QPointF(rect.width() + spacing, 0)

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

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

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

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

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

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemSelectedChange:
            selected = bool(qtcompat.qunwrap(value))
            self.shapeItem.setSelected(selected)
            self.captionTextItem.setSelectionState(selected)
            self.backgroundItem.setVisible(selected)
        elif change == QGraphicsItem.ItemPositionHasChanged:
            self.positionChanged.emit()

        return QGraphicsObject.itemChange(self, change, value)