Exemplo n.º 1
0
class KColorWidget(QtWidgets.QLabel):
    """Custom Label widget to display color settings."""

    clicked = QtCore.Signal(bool)
    colorChanged = QtCore.Signal(QtGui.QColor)

    def __init__(self, parent, color):
        super(KColorWidget, self).__init__(parent)
        self.installEventFilter(self)
        self._color = QtGui.QColor(color)

        self.pixmap = QtGui.QPixmap(12, 12)
        self.pixmap.fill(self._color)

        self.setProperty('colorLabel', True)
        self.setFixedSize(24, 24)
        self.setScaledContents(True)
        self.setPixmap(self.pixmap)

        self.createConnections()

    def createConnections(self):
        self.clicked.connect(self.openColorDialog)
        self.colorChanged.connect(self.changeColor)

    def eventFilter(self, object, event):
        if event.type() == QtCore.QEvent.Enter:
            self.setCursor(QtCore.Qt.PointingHandCursor)
            return True

        if event.type() == QtCore.QEvent.Leave:
            self.setCursor(QtCore.Qt.ArrowCursor)
            return True

        if event.type() == QtCore.QEvent.MouseButtonPress:
            self.setCursor(QtCore.Qt.ClosedHandCursor)
            return True

        if event.type() == QtCore.QEvent.MouseButtonRelease:
            self.setCursor(QtCore.Qt.PointingHandCursor)
            self.clicked.emit(True)
            return True

        return False

    def openColorDialog(self):

        colorDialog = QtWidgets.QColorDialog()
        colorDialog.setOption(QtWidgets.QColorDialog.DontUseNativeDialog, True)
        newColor = colorDialog.getColor(self._color, self)
        if newColor.isValid():
            self._color = newColor
            self.colorChanged.emit(newColor)

    def changeColor(self, color):
        self.pixmap.fill(color)
        self.setPixmap(self.pixmap)
Exemplo n.º 2
0
class RigNameLabel(QtWidgets.QLabel):

    clicked = QtCore.Signal()

    def __init__(self, parent=None):
        super(RigNameLabel, self).__init__(parent)
        self.setObjectName('rigNameLabel')
        self.setToolTip('Double Click to Edit')

    def mouseDoubleClickEvent(self, event):
        self.clicked.emit()
Exemplo n.º 3
0
class GraphViewWidget(QtWidgets.QWidget):

    rigNameChanged = QtCore.Signal()

    def __init__(self, parent=None):

        # constructors of base classes
        super(GraphViewWidget, self).__init__(parent)
        self.openedFile = None
        self.setObjectName('graphViewWidget')
        self.setAttribute(QtCore.Qt.WA_WindowPropagation, True)

    def setGraphView(self, graphView):

        self.graphView = graphView

        # Setup Layout
        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.graphView)
        self.setLayout(layout)

        #########################
        ## Setup hotkeys for the following actions.
        deleteShortcut = QtWidgets.QShortcut(
            QtGui.QKeySequence(QtCore.Qt.Key_Delete), self)
        deleteShortcut.activated.connect(self.graphView.deleteSelectedNodes)

        frameShortcut = QtWidgets.QShortcut(
            QtGui.QKeySequence(QtCore.Qt.Key_F), self)
        frameShortcut.activated.connect(self.graphView.frameSelectedNodes)

        frameShortcut = QtWidgets.QShortcut(
            QtGui.QKeySequence(QtCore.Qt.Key_A), self)
        frameShortcut.activated.connect(self.graphView.frameAllNodes)

    def getGraphView(self):
        return self.graphView
Exemplo n.º 4
0
class UndoRedoManager(QtCore.QObject):
    """An UndoManager manages the undo/redo stack for an application.
    Usually only a single undo manager is instantiated for a given application,
    but it is possible to instantiate multiple undomanagers, each one responsible for a separate undo stack.

    """

    __instance = None
    undoUpdated = QtCore.Signal(object)

    def __init__(self):
        super(UndoRedoManager, self).__init__()
        self.__undoStack = []
        self.__redoStack = []
        self.__currentBracket = None
        self.__isUndoingOrRedoing = False
        self.__enabled = True

        self.__fireUpdateCallback()

    def enable(self):
        """Enables the UndoManager so that new brackets can be opened and commands added"""
        self.__enabled = True

    def disable(self):
        """Disables the UndoManager so that no new brackets can be opened or commands added"""
        self.__enabled = False

    def enabled(self):
        """Returns true if the UndoManager is enabled"""
        return self.__enabled

    def canAddCommand(self):
        """Returns True if the undo manager is in a state where a command can be added.
        A command can only be added if the undo manager is enabled, a bracket has been opened, and the undo manager is not currently undoing or redoing commands.
        """
        return self.__enabled and not self.__isUndoingOrRedoing

    def isUndoingOrRedoing(self):
        """Returns true if the undo manager is currently undoing or redoing."""
        return self.__isUndoingOrRedoing

    def addCommand(self, command, invokeRedoOnAdd=False):
        """
        Adds a new command to the currently opened undo bracket.
        :param command: A command object which encapsulates the revertable action.
        """

        if not self.canAddCommand():
            raise Exception("Cannot add command when undo manager is disabled")

        if self.__currentBracket:
            self.__currentBracket.addCommand(command,
                                             invokeRedoOnAdd=invokeRedoOnAdd)
        else:
            if invokeRedoOnAdd:
                command.redo()
            if len(self.__undoStack) > 0:
                if self.__undoStack[len(self.__undoStack) -
                                    1].mergeWith(command):
                    # the command was merged with the previous, so does not need to be applied separately
                    return

            self.__undoStack.append(command)

        self.__fireUpdateCallback()

    def openBracket(self, desc):
        """Opens a new undo bracket so that subsequent updo commands are added to the new bracket.

        When a command bracket it opened, all subsequent commands are added to the
        command bracket, which will be treated as a single undo on command the stack.
        openBracket can be called multiple times creating nested brackets. For each call to openBracket,
        closeBracket must also be called to ensure the undo manager is left in a valid state.

        :param desc: A string to describe the new bracket. This string can be used to populate undo widgets.
        """

        if not self.canAddCommand():
            raise Exception(
                "Cannot open bracket when undo manager is disabled")

        self.__currentBracket = CommandBracket(desc, self.__currentBracket)

        if not self.__currentBracket.getParentCommandBracket():
            # Append the command if it is a root command bracket.
            self.__undoStack.append(self.__currentBracket)
        self.__clearRedoStack()

        # print ">>>openBracket:" + desc

    def closeBracket(self):
        """
        Closes the currently open undo bracket, encapsulating all added commands into a single undable action.
        If multiple levels of brackets have been opened, the parent bracked is made the current active bracket.
        """
        assert not self.__currentBracket is None, "UndoRedoManager.closeBracket() called but bracket has not been opened"
        # print "<<<closeBracket:" + self.__currentBracket.shortDesc()

        if self.__currentBracket.getNumCommands() == 0:
            print "Warning: UndoBracket closed with no commands added:" + self.__currentBracket.shortDesc(
            )
            if self.__currentBracket.getParentCommandBracket() is not None:
                self.__currentBracket.getParentCommandBracket().popCommand()
            else:
                self.__undoStack.pop()
            # When a bracket is closed, the parent command backet is re-instated.
            self.__currentBracket = self.__currentBracket.getParentCommandBracket(
            )
        else:
            self.__currentBracket.finalize()
            # When a bracket is closed, the parent command backet is re-instated.
            self.__currentBracket = self.__currentBracket.getParentCommandBracket(
            )
            if not self.__currentBracket:
                # Fire the update only if the root level command bracket is closed.
                self.__fireUpdateCallback()

        # import inspect
        # for frame, filename, line_num, func, source_code, source_index in inspect.stack():
        #   print "stack :" + (filename) + ":" + str(func) + ":" + str(source_code)

    def cancelBracket(self):
        """
        Cancels the currently open bracket, reverting all changes added to the bracket since openBracket was called.
        """
        assert not self.__currentBracket is None, "UndoRedoManager.cancelBracket() called but bracket has not been opened"
        #print "<<<closeBracket:" + self.__currentBracket.shortDesc()
        self.closeBracket()
        self.undo()
        command = self.__redoStack.pop()
        command.destroy()
        self.__fireUpdateCallback()
        #

    def bracketOpened(self):
        """Returns True if a bracket has been opened.
        """
        return not self.__currentBracket is None

    def haveUndo(self):
        """Returns Ture if the undo stack currently contains an undoable action"""
        return len(self.__undoStack) > 0

    def canUndo(self):
        """Returns Ture if the undo stack currently contains an undoable action"""
        return self.haveUndo()

    def undo(self):
        """Reverts the action at the top of the undo stack"""
        assert self.haveUndo(
        ), "UndoRedoManager.undo() called but UndoRedoManager.haveUndo() is false"
        self.__isUndoingOrRedoing = True
        command = self.__undoStack.pop()
        command.undo()
        self.__redoStack.append(command)
        self.__fireUpdateCallback()
        self.__isUndoingOrRedoing = False

    def haveRedo(self):
        """Returns Ture if the redo stack currently contains an redoable action"""
        return len(self.__redoStack) > 0

    def canRedo(self):
        """Returns Ture if the redo stack currently contains an redoable action"""
        return self.haveRedo()

    def redo(self):
        """Reapplies the action at the top of the redo stack"""
        self.__isUndoingOrRedoing = True
        command = self.__redoStack.pop()
        command.redo()
        self.__undoStack.append(command)
        self.__fireUpdateCallback()
        self.__isUndoingOrRedoing = False

    def __clearUndoStack(self):
        for command in self.__undoStack:
            command.destroy()
        self.__undoStack = []

    def __clearRedoStack(self):
        for command in self.__redoStack:
            command.destroy()
        self.__redoStack = []

    def reset(self):
        """Resets the undo manager, clearing both the undo and redo stacks"""
        self.__clearUndoStack()
        self.__clearRedoStack()
        self.__fireUpdateCallback()

    def destroy(self):
        """Destroys all data in the undo manager."""
        self.reset()

    def __fireUpdateCallback(self):

        if self.haveUndo():
            undoShortDesc = self.__undoStack[-1].shortDesc()
        else:
            undoShortDesc = None

        if self.haveRedo():
            redoShortDesc = self.__redoStack[-1].shortDesc()
        else:
            redoShortDesc = None

        self.undoUpdated.emit({
            'undoShortDesc': undoShortDesc,
            'canUndo': self.haveUndo(),
            'redoShortDesc': redoShortDesc,
            'canRedo': self.haveRedo()
        })

    def logDebug(self):
        """Prints debug strings to help debug the state of the undo manager"""
        print "bracketOpened:" + str(self.bracketOpened())
        print "undoStack:"
        for command in self.__undoStack:
            command.logDebug(1)
        print "redoStack:"
        for command in self.__redoStack:
            command.logDebug(1)
        print "-------------"

    @classmethod
    def getInstance(cls):
        if cls.__instance is None:
            cls.__instance = UndoRedoManager()
        return cls.__instance
Exemplo n.º 5
0
class KGraphViewWidget(GraphViewWidget):

    rigNameChanged = QtCore.Signal()
    rigLoaded = QtCore.Signal(object)
    rigLoadedConfig = QtCore.Signal(object)

    def __init__(self, parent=None):

        # constructors of base classes
        super(KGraphViewWidget, self).__init__(parent)

        self._builder = None
        self._guideBuilder = None
        self.guideRig = None

        graphView = KGraphView(parent=self)
        graphView.nodeAdded.connect(self.__onNodeAdded)
        graphView.nodeRemoved.connect(self.__onNodeRemoved)
        graphView.beginConnectionManipulation.connect(
            self.__onBeginConnectionManipulation)
        graphView.endConnectionManipulation.connect(
            self.__onEndConnectionManipulationSignal)
        graphView.connectionAdded.connect(self.__onConnectionAdded)
        graphView.connectionRemoved.connect(self.__onConnectionRemoved)

        graphView.selectionChanged.connect(self.__onSelectionChanged)
        graphView.endSelectionMoved.connect(self.__onSelectionMoved)

        graphView.beginDeleteSelection.connect(self.__onBeginDeleteSelection)
        graphView.endDeleteSelection.connect(self.__onEndDeleteSelection)

        self.setGraphView(graphView)

        # =========================================
        # Setup hotkeys for the following actions.
        # =========================================

        undoShortcut = QtWidgets.QShortcut(
            QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_Z), self)
        undoShortcut.activated.connect(self.undo)

        redoShortcut = QtWidgets.QShortcut(
            QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_Y), self)
        redoShortcut.activated.connect(self.redo)

        openContextualNodeListShortcut = QtWidgets.QShortcut(
            QtGui.QKeySequence(QtCore.Qt.Key_Tab), self)
        openContextualNodeListShortcut.activated.connect(
            self.openContextualNodeList)

        self.newRigPreset()

    # ============
    # Rig Methods
    # ============
    def editRigName(self):
        dialog = QtWidgets.QInputDialog(self)
        dialog.setObjectName('RigNameDialog')
        text, ok = dialog.getText(self,
                                  'Edit Rig Name',
                                  'New Rig Name',
                                  text=self.guideRig.getName())

        if ok is True:
            self.setGuideRigName(text)

    def setGuideRigName(self, text):
        if text.endswith('_guide') is True:
            text = text.replace('_guide', '')

        self.guideRig.setName(text)
        self.rigNameChanged.emit()

    def newRigPreset(self):
        try:
            self.guideRig = Rig()
            self.getGraphView().displayGraph(self.guideRig)
            self.setGuideRigName('MyRig')

            self.openedFile = None

            self.window().setWindowTitle('Kraken Editor')

            logger.inform("New Rig Created")
        except:
            logger.exception("Error Creating New Rig")

    def saveRig(self, saveAs=False):
        """Saves the current rig to disc.

        Args:
            saveAs (Boolean): Determines if this was a save as call or just a normal save.

        Returns:
            String: Path to the saved file.

        """

        try:
            self.window().setCursor(QtCore.Qt.WaitCursor)

            filePath = self.openedFile

            if saveAs is True or not filePath or not os.path.isdir(
                    os.path.dirname(filePath)):

                settings = self.window().getSettings()
                settings.beginGroup('Files')
                lastFilePath = settings.value(
                    "lastFilePath",
                    os.path.join(GetKrakenPath(), self.guideRig.getName()))
                settings.endGroup()

                filePathDir = os.path.dirname(lastFilePath)

                if not os.path.isdir(filePathDir):
                    filePathDir = GetKrakenPath()

                fileDialog = QtWidgets.QFileDialog(self)
                fileDialog.setOption(QtWidgets.QFileDialog.DontUseNativeDialog,
                                     on=True)
                fileDialog.setWindowTitle('Save Rig Preset As')
                fileDialog.setDirectory(os.path.abspath(filePathDir))
                fileDialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)
                fileDialog.setNameFilter('Kraken Rig (*.krg)')
                fileDialog.setDefaultSuffix('krg')

                if fileDialog.exec_() == QtWidgets.QFileDialog.Accepted:
                    filePath = fileDialog.selectedFiles()[0]
                else:
                    return False

            self.synchGuideRig()

            # Backdrop Meta Data
            graphView = self.getGraphView()
            backdropNodes = graphView.getNodesOfType('KBackdrop')
            backdropData = [x.getData() for x in backdropNodes]

            # =====================
            # Add Meta Data to rig
            # =====================
            self.guideRig.setMetaDataItem('backdrops', backdropData)

            currConfig = Config.getInstance()
            if currConfig.getModulePath(
            ) == "kraken.core.configs.config.Config":
                self.guideRig.setMetaDataItem('config', "Default Config")
            else:
                self.guideRig.setMetaDataItem('config',
                                              currConfig.getModulePath())

            # Write rig file
            try:
                self.guideRig.writeRigDefinitionFile(filePath)
                settings = self.window().getSettings()
                settings.beginGroup('Files')
                settings.setValue("lastFilePath", filePath)
                settings.endGroup()
                self.openedFile = filePath

                logger.inform('Saved Rig file: ' + filePath)

            except:
                logger.exception('Error Saving Rig File')
                return False

            return filePath

        finally:
            self.window().setCursor(QtCore.Qt.ArrowCursor)

    def saveAsRigPreset(self):
        """Opens a dialogue window to save the current rig as a different file."""

        filePath = self.saveRig(saveAs=True)
        if filePath is not False:
            self.window().setWindowTitle('Kraken Editor - ' + filePath + '[*]')

        self.rigLoaded.emit(self.openedFile)

    def saveRigPreset(self):

        if self.openedFile is None or not os.path.exists(self.openedFile):
            self.saveAsRigPreset()

        else:
            self.saveRig(saveAs=False)
            self.rigLoaded.emit(self.openedFile)

    def openRigPreset(self):

        try:
            self.window().setCursor(QtCore.Qt.WaitCursor)

            settings = self.window().getSettings()
            settings.beginGroup('Files')
            lastFilePath = settings.value(
                "lastFilePath",
                os.path.join(GetKrakenPath(), self.guideRig.getName()))
            settings.endGroup()

            if not lastFilePath:
                lastFilePath = GetKrakenPath()

            fileDialog = QtWidgets.QFileDialog(self)
            fileDialog.setOption(QtWidgets.QFileDialog.DontUseNativeDialog,
                                 on=True)
            fileDialog.setWindowTitle('Open Rig Preset')
            fileDialog.setDirectory(
                os.path.dirname(os.path.abspath(lastFilePath)))
            fileDialog.setAcceptMode(QtWidgets.QFileDialog.AcceptOpen)
            fileDialog.setNameFilter('Kraken Rig (*.krg)')

            if fileDialog.exec_() == QtWidgets.QFileDialog.Accepted:
                filePath = fileDialog.selectedFiles()[0]
                self.loadRigPreset(filePath)

        finally:
            self.window().setCursor(QtCore.Qt.ArrowCursor)

    def loadRigPreset(self, filePath):

        if not os.path.exists(filePath):
            logger.warn("File '" + filePath + "' does not exist!")
            return

        self.guideRig = Rig()
        self.guideRig.loadRigDefinitionFile(filePath)

        self.setGuideRigName(self.guideRig.getName())

        rigConfig = self.guideRig.getMetaDataItem('config')
        if rigConfig is not None:
            self.rigLoadedConfig.emit(rigConfig)

        self.graphView.displayGraph(self.guideRig)

        settings = self.window().getSettings()
        settings.beginGroup('Files')
        settings.setValue("lastFilePath", filePath)
        settings.endGroup()

        self.openedFile = filePath
        self.window().setWindowTitle('Kraken Editor - ' + filePath + '[*]')
        logger.inform('Loaded Rig file: ' + filePath)

        self.rigLoaded.emit(filePath)

    def buildGuideRig(self):

        try:
            logger.info('Building Guide')
            self.window().setCursor(QtCore.Qt.WaitCursor)

            initConfigIndex = self.window(
            ).krakenMenu.configsWidget.currentIndex()

            self.synchGuideRig()

            # Append "_guide" to rig name when building guide
            if self.guideRig.getName().endswith('_guide') is False:
                self.guideRig.setName(self.guideRig.getName() + '_guide')

            if self.window().preferences.getPreferenceValue(
                    'delete_existing_rigs'):
                if self._guideBuilder:
                    self._guideBuilder.deleteBuildElements()

            self._guideBuilder = plugins.getBuilder()
            self._guideBuilder.build(self.guideRig)

            logger.inform('Guide Rig Build Success')

            self.window().krakenMenu.setCurrentConfig(initConfigIndex)

        except:
            logger.exception('Error Building')

        finally:
            self.window().setCursor(QtCore.Qt.ArrowCursor)

    def synchGuideRig(self):
        synchronizer = plugins.getSynchronizer()

        # Guide is always  built with "_guide" need this so synchronizer not confused with real Rig nodes
        if self.guideRig.getName().endswith('_guide') is False:
            self.guideRig.setName(self.guideRig.getName() + '_guide')

        synchronizer.setTarget(self.guideRig)
        synchronizer.sync()

    def buildRig(self):

        try:
            self.window().setCursor(QtCore.Qt.WaitCursor)

            self.window().statusBar.showMessage('Building Rig')

            initConfigIndex = self.window(
            ).krakenMenu.configsWidget.currentIndex()

            self.synchGuideRig()

            rigBuildData = self.guideRig.getRigBuildData()
            rig = Rig()
            rig.loadRigDefinition(rigBuildData)

            rig.setName(rig.getName().replace('_guide', ''))

            if self.window().preferences.getPreferenceValue(
                    'delete_existing_rigs'):
                if self._builder:
                    self._builder.deleteBuildElements()

            self._builder = plugins.getBuilder()
            self._builder.build(rig)

            logger.inform('Rig Build Success')

            self.window().krakenMenu.setCurrentConfig(initConfigIndex)

        except Exception as e:
            logger.exception('Error Building')

        finally:
            self.window().setCursor(QtCore.Qt.ArrowCursor)

    # ==========
    # Shortcuts
    # ==========
    def copy(self):
        graphView = self.getGraphView()
        pos = graphView.getSelectedNodesCentroid()
        graphView.copySettings(pos)

    def paste(self):
        graphView = self.getGraphView()
        clipboardData = self.graphView.getClipboardData()

        pos = clipboardData['copyPos'] + QtCore.QPoint(20, 20)
        graphView.pasteSettings(pos,
                                mirrored=False,
                                createConnectionsToExistingNodes=True)

    def pasteUnconnected(self):
        graphView = self.getGraphView()
        clipboardData = self.graphView.getClipboardData()

        pos = clipboardData['copyPos'] + QtCore.QPoint(20, 20)
        graphView.pasteSettings(pos,
                                mirrored=False,
                                createConnectionsToExistingNodes=False)

    def pasteMirrored(self):
        graphView = self.getGraphView()
        clipboardData = self.graphView.getClipboardData()

        pos = clipboardData['copyPos'] + QtCore.QPoint(20, 20)
        graphView.pasteSettings(pos,
                                mirrored=True,
                                createConnectionsToExistingNodes=False)

    def pasteMirroredConnected(self):
        graphView = self.getGraphView()
        clipboardData = self.graphView.getClipboardData()

        pos = clipboardData['copyPos'] + QtCore.QPoint(20, 20)
        graphView.pasteSettings(pos,
                                mirrored=True,
                                createConnectionsToExistingNodes=True)

    def undo(self):
        UndoRedoManager.getInstance().undo()

    def redo(self):
        UndoRedoManager.getInstance().redo()

    def openContextualNodeList(self):
        pos = self.mapFromGlobal(QtGui.QCursor.pos())

        contextualNodeList = ContextualNodeList(self)

        scenepos = self.graphView.mapToScene(pos)
        contextualNodeList.showAtPos(pos, scenepos, self.graphView)

    # ==============
    # Other Methods
    # ==============
    def addBackdrop(self, name='Backdrop'):
        """Adds a backdrop node to the graph.

        Args:
            name (str): Name of the backdrop node.

        Returns:
            Node: Backdrop node that was created.

        """

        graphView = self.getGraphView()

        initName = name
        suffix = 1
        collision = True
        while collision:

            collision = graphView.hasNode(name)
            if not collision:
                break

            result = re.split(r"(\d+)$", initName, 1)
            if len(result) > 1:
                initName = result[0]
                suffix = int(result[1])

            name = initName + str(suffix).zfill(2)
            suffix += 1

        backdropNode = KBackdrop(graphView, name)
        graphView.addNode(backdropNode)

        graphView.selectNode(backdropNode, clearSelection=True)

        return backdropNode

    # ===============
    # Signal Handlers
    # ===============
    def __onNodeAdded(self, node):
        if not UndoRedoManager.getInstance().isUndoingOrRedoing():
            command = graph_commands.AddNodeCommand(self.graphView,
                                                    self.guideRig, node)
            UndoRedoManager.getInstance().addCommand(command)

    def __onNodeRemoved(self, node):

        if type(node).__name__ != 'KBackdrop':
            node.getComponent().detach()

        if not UndoRedoManager.getInstance().isUndoingOrRedoing():
            command = graph_commands.RemoveNodeCommand(self.graphView,
                                                       self.guideRig, node)
            UndoRedoManager.getInstance().addCommand(command)

    def __onBeginConnectionManipulation(self):
        UndoRedoManager.getInstance().openBracket('Connect Ports')

    def __onEndConnectionManipulationSignal(self):
        UndoRedoManager.getInstance().closeBracket()

    def __onConnectionAdded(self, connection):
        if not UndoRedoManager.getInstance().isUndoingOrRedoing():
            command = graph_commands.ConnectionAddedCommand(
                self.graphView, self.guideRig, connection)
            UndoRedoManager.getInstance().addCommand(command)

    def __onConnectionRemoved(self, connection):
        if not UndoRedoManager.getInstance().isUndoingOrRedoing():
            command = graph_commands.ConnectionRemovedCommand(
                self.graphView, self.guideRig, connection)
            UndoRedoManager.getInstance().addCommand(command)

    def __onSelectionChanged(self, deselectedNodes, selectedNodes):
        if not UndoRedoManager.getInstance().isUndoingOrRedoing():
            command = graph_commands.SelectionChangeCommand(
                self.graphView, deselectedNodes, selectedNodes)
            UndoRedoManager.getInstance().addCommand(command)

    def __onSelectionMoved(self, nodes, delta):
        if not UndoRedoManager.getInstance().isUndoingOrRedoing():
            command = graph_commands.NodesMoveCommand(self.graphView, nodes,
                                                      delta)
            UndoRedoManager.getInstance().addCommand(command)

    def __onBeginDeleteSelection(self):
        UndoRedoManager.getInstance().openBracket('Delete Nodes')

    def __onEndDeleteSelection(self):
        UndoRedoManager.getInstance().closeBracket()
Exemplo n.º 6
0
class GraphView(QtWidgets.QGraphicsView):

    nodeAdded = QtCore.Signal(Node)
    nodeRemoved = QtCore.Signal(Node)
    nodeNameChanged = QtCore.Signal(str, str)
    beginDeleteSelection = QtCore.Signal()
    endDeleteSelection = QtCore.Signal()

    beginConnectionManipulation = QtCore.Signal()
    endConnectionManipulation = QtCore.Signal()
    connectionAdded = QtCore.Signal(Connection)
    connectionRemoved = QtCore.Signal(Connection)

    beginNodeSelection = QtCore.Signal()
    endNodeSelection = QtCore.Signal()
    selectionChanged = QtCore.Signal(list, list)

    # During the movement of the nodes, this signal is emitted with the incremental delta.
    selectionMoved = QtCore.Signal(set, QtCore.QPointF)

    # After moving the nodes interactively, this signal is emitted with the final delta.
    endSelectionMoved = QtCore.Signal(set, QtCore.QPointF)

    _clipboardData = None

    _backgroundColor = QtGui.QColor(50, 50, 50)
    _gridPenS = QtGui.QPen(QtGui.QColor(44, 44, 44, 255), 0.5)
    _gridPenL = QtGui.QPen(QtGui.QColor(40, 40, 40, 255), 1.0)
    _gridSizeFine = 30
    _gridSizeCourse = 300

    _mouseWheelZoomRate = 0.0005

    _snapToGrid = False

    def __init__(self, parent=None):
        super(GraphView, self).__init__(parent)
        self.setObjectName('graphView')

        self.__graphViewWidget = parent

        self.setRenderHint(QtGui.QPainter.Antialiasing)
        self.setRenderHint(QtGui.QPainter.TextAntialiasing)

        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)

        # Explicitly set the scene rect. This ensures all view parameters will be explicitly controlled
        # in the event handlers of this class.
        size = QtCore.QSize(600, 400)
        self.resize(size)
        self.setSceneRect(-size.width() * 0.5, -size.height() * 0.5,
                          size.width(), size.height())

        self.setAcceptDrops(True)
        self.reset()

    def getGraphViewWidget(self):
        return self.__graphViewWidget

    ################################################
    ## Graph
    def reset(self):
        self.setScene(QtWidgets.QGraphicsScene())

        self.__connections = set()
        self.__nodes = {}
        self.__selection = set()

        self._manipulationMode = MANIP_MODE_NONE
        self._selectionRect = None

    def getGridSize(self):
        """Gets the size of the grid of the graph.

        Returns:
            int: Size of the grid.

        """

        return self._gridSizeFine

    def getSnapToGrid(self):
        """Gets the snap to grid value.

        Returns:
            Boolean: Whether snap to grid is active or not.

        """

        return self._snapToGrid

    def setSnapToGrid(self, snap):
        """Sets the snap to grid value.

        Args:
            snap (Boolean): True to snap to grid, false not to.

        """

        self._snapToGrid = snap

    ################################################
    ## Nodes

    def addNode(self, node, emitSignal=True):
        self.scene().addItem(node)
        self.__nodes[node.getName()] = node
        node.nameChanged.connect(self._onNodeNameChanged)

        if emitSignal:
            self.nodeAdded.emit(node)

        return node

    def removeNode(self, node, emitSignal=True):

        del self.__nodes[node.getName()]
        self.scene().removeItem(node)
        node.nameChanged.disconnect(self._onNodeNameChanged)

        if emitSignal:
            self.nodeRemoved.emit(node)

    def hasNode(self, name):
        return name in self.__nodes

    def getNode(self, name):
        if name in self.__nodes:
            return self.__nodes[name]
        return None

    def getNodes(self):
        return self.__nodes

    def _onNodeNameChanged(self, origName, newName):
        if newName in self.__nodes and self.__nodes[origName] != self.__nodes[
                newName]:
            raise Exception("New name collides with existing node.")
        node = self.__nodes[origName]
        self.__nodes[newName] = node
        del self.__nodes[origName]
        self.nodeNameChanged.emit(origName, newName)

    def clearSelection(self, emitSignal=True):

        prevSelection = []
        if emitSignal:
            for node in self.__selection:
                prevSelection.append(node)

        for node in self.__selection:
            node.setSelected(False)
        self.__selection.clear()

        if emitSignal and len(prevSelection) != 0:
            self.selectionChanged.emit(prevSelection, [])

    def selectNode(self, node, clearSelection=False, emitSignal=True):
        prevSelection = []
        if emitSignal:
            for n in self.__selection:
                prevSelection.append(n)

        if clearSelection is True:
            self.clearSelection(emitSignal=False)

        if node in self.__selection:
            raise IndexError("Node is already in selection!")

        node.setSelected(True)
        self.__selection.add(node)

        if emitSignal:

            newSelection = []
            for n in self.__selection:
                newSelection.append(n)

            self.selectionChanged.emit(prevSelection, newSelection)

    def deselectNode(self, node, emitSignal=True):

        if node not in self.__selection:
            raise IndexError("Node is not in selection!")

        prevSelection = []
        if emitSignal:
            for n in self.__selection:
                prevSelection.append(n)

        node.setSelected(False)
        self.__selection.remove(node)

        if emitSignal:
            newSelection = []
            for n in self.__selection:
                newSelection.append(n)

            self.selectionChanged.emit(prevSelection, newSelection)

    def getSelectedNodes(self):
        return self.__selection

    def deleteSelectedNodes(self):
        self.beginDeleteSelection.emit()

        selectedNodes = self.getSelectedNodes()
        names = ""
        for node in selectedNodes:
            node.disconnectAllPorts()
            self.removeNode(node)

        self.endDeleteSelection.emit()

    def frameNodes(self, nodes):
        if len(nodes) == 0:
            return

        def computeWindowFrame():
            windowRect = self.rect()
            windowRect.setLeft(windowRect.left() + 16)
            windowRect.setRight(windowRect.right() - 16)
            windowRect.setTop(windowRect.top() + 16)
            windowRect.setBottom(windowRect.bottom() - 16)
            return windowRect

        nodesRect = None
        for node in nodes:
            nodeRectF = node.transform().mapRect(node.rect())
            nodeRect = QtCore.QRect(nodeRectF.x(), nodeRectF.y(),
                                    nodeRectF.width(), nodeRectF.height())
            if nodesRect is None:
                nodesRect = nodeRect
            else:
                nodesRect = nodesRect.united(nodeRect)

        windowRect = computeWindowFrame()

        scaleX = float(windowRect.width()) / float(nodesRect.width())
        scaleY = float(windowRect.height()) / float(nodesRect.height())
        if scaleY > scaleX:
            scale = scaleX
        else:
            scale = scaleY

        if scale < 1.0:
            self.setTransform(QtGui.QTransform.fromScale(scale, scale))
        else:
            self.setTransform(QtGui.QTransform())

        sceneRect = self.sceneRect()
        pan = sceneRect.center() - nodesRect.center()
        sceneRect.translate(-pan.x(), -pan.y())
        self.setSceneRect(sceneRect)

        # Update the main panel when reframing.
        self.update()

    def frameSelectedNodes(self):
        self.frameNodes(self.getSelectedNodes())

    def frameAllNodes(self):
        allnodes = []
        for name, node in self.__nodes.iteritems():
            allnodes.append(node)
        self.frameNodes(allnodes)

    def getSelectedNodesCentroid(self):
        selectedNodes = self.getSelectedNodes()

        leftMostNode = None
        topMostNode = None
        for node in selectedNodes:
            nodePos = node.getGraphPos()

            if leftMostNode is None:
                leftMostNode = node
            else:
                if nodePos.x() < leftMostNode.getGraphPos().x():
                    leftMostNode = node

            if topMostNode is None:
                topMostNode = node
            else:
                if nodePos.y() < topMostNode.getGraphPos().y():
                    topMostNode = node

        xPos = leftMostNode.getGraphPos().x()
        yPos = topMostNode.getGraphPos().y()
        pos = QtCore.QPoint(xPos, yPos)

        return pos

    def moveSelectedNodes(self, delta, emitSignal=True):
        for node in self.__selection:
            node.translate(delta.x(), delta.y())

        if emitSignal:
            self.selectionMoved.emit(self.__selection, delta)

    # After moving the nodes interactively, this signal is emitted with the final delta.
    def endMoveSelectedNodes(self, delta):
        self.endSelectionMoved.emit(self.__selection, delta)

    ################################################
    ## Connections

    def emitBeginConnectionManipulationSignal(self):
        self.beginConnectionManipulation.emit()

    def emitEndConnectionManipulationSignal(self):
        self.endConnectionManipulation.emit()

    def addConnection(self, connection, emitSignal=True):

        self.__connections.add(connection)
        self.scene().addItem(connection)
        if emitSignal:
            self.connectionAdded.emit(connection)
        return connection

    def removeConnection(self, connection, emitSignal=True):

        connection.disconnect()
        self.__connections.remove(connection)
        self.scene().removeItem(connection)
        if emitSignal:
            self.connectionRemoved.emit(connection)

    def connectPorts(self, srcNode, outputName, tgtNode, inputName):

        if isinstance(srcNode, Node):
            sourceNode = srcNode
        elif isinstance(srcNode, basestring):
            sourceNode = self.getNode(srcNode)
            if not sourceNode:
                raise Exception("Node not found:" + str(srcNode))
        else:
            raise Exception("Invalid srcNode:" + str(srcNode))

        sourcePort = sourceNode.getOutputPort(outputName)
        if not sourcePort:
            raise Exception("Node '" + sourceNode.getName() +
                            "' does not have output:" + outputName)

        if isinstance(tgtNode, Node):
            targetNode = tgtNode
        elif isinstance(tgtNode, basestring):
            targetNode = self.getNode(tgtNode)
            if not targetNode:
                raise Exception("Node not found:" + str(tgtNode))
        else:
            raise Exception("Invalid tgtNode:" + str(tgtNode))

        targetPort = targetNode.getInputPort(inputName)
        if not targetPort:
            raise Exception("Node '" + targetNode.getName() +
                            "' does not have input:" + inputName)

        connection = Connection(self, sourcePort.outCircle(),
                                targetPort.inCircle())
        self.addConnection(connection, emitSignal=False)

        return connection

    ################################################
    ## Events

    def mousePressEvent(self, event):

        if event.button() is QtCore.Qt.MouseButton.LeftButton and self.itemAt(
                event.pos()) is None:
            self.beginNodeSelection.emit()
            self._manipulationMode = MANIP_MODE_SELECT
            self._mouseDownSelection = copy.copy(self.getSelectedNodes())
            self._selectionRect = SelectionRect(graph=self,
                                                mouseDownPos=self.mapToScene(
                                                    event.pos()))

        elif event.button() is QtCore.Qt.MouseButton.MiddleButton:
            self.setCursor(QtCore.Qt.OpenHandCursor)
            self._manipulationMode = MANIP_MODE_PAN
            self._lastPanPoint = self.mapToScene(event.pos())

        elif event.button() is QtCore.Qt.MouseButton.RightButton:
            self.setCursor(QtCore.Qt.SizeHorCursor)
            self._manipulationMode = MANIP_MODE_ZOOM
            self._lastZoomPoint = self.mapToScene(event.pos())
            self._lastTransform = QtGui.QTransform(self.transform())

        else:
            super(GraphView, self).mousePressEvent(event)

    def mouseMoveEvent(self, event):
        modifiers = QtWidgets.QApplication.keyboardModifiers()

        if self._manipulationMode == MANIP_MODE_SELECT:
            dragPoint = self.mapToScene(event.pos())
            self._selectionRect.setDragPoint(dragPoint)

            # This logic allows users to use ctrl and shift with rectangle
            # select to add / remove nodes.
            if modifiers == QtCore.Qt.ControlModifier:
                for name, node in self.__nodes.iteritems():

                    if node in self._mouseDownSelection:
                        if node.isSelected(
                        ) and self._selectionRect.collidesWithItem(node):
                            self.deselectNode(node, emitSignal=False)
                        elif not node.isSelected(
                        ) and not self._selectionRect.collidesWithItem(node):
                            self.selectNode(node, emitSignal=False)
                    else:
                        if not node.isSelected(
                        ) and self._selectionRect.collidesWithItem(node):
                            self.selectNode(node, emitSignal=False)
                        elif node.isSelected(
                        ) and not self._selectionRect.collidesWithItem(node):
                            if node not in self._mouseDownSelection:
                                self.deselectNode(node, emitSignal=False)

            elif modifiers == QtCore.Qt.ShiftModifier:
                for name, node in self.__nodes.iteritems():
                    if not node.isSelected(
                    ) and self._selectionRect.collidesWithItem(node):
                        self.selectNode(node, emitSignal=False)
                    elif node.isSelected(
                    ) and not self._selectionRect.collidesWithItem(node):
                        if node not in self._mouseDownSelection:
                            self.deselectNode(node, emitSignal=False)

            else:
                self.clearSelection(emitSignal=False)

                for name, node in self.__nodes.iteritems():
                    if not node.isSelected(
                    ) and self._selectionRect.collidesWithItem(node):
                        self.selectNode(node, emitSignal=False)
                    elif node.isSelected(
                    ) and not self._selectionRect.collidesWithItem(node):
                        self.deselectNode(node, emitSignal=False)

        elif self._manipulationMode == MANIP_MODE_PAN:
            delta = self.mapToScene(event.pos()) - self._lastPanPoint

            rect = self.sceneRect()
            rect.translate(-delta.x(), -delta.y())
            self.setSceneRect(rect)

            self._lastPanPoint = self.mapToScene(event.pos())

        elif self._manipulationMode == MANIP_MODE_MOVE:

            newPos = self.mapToScene(event.pos())
            delta = newPos - self._lastDragPoint
            self._lastDragPoint = newPos

            selectedNodes = self.getSelectedNodes()

            # Apply the delta to each selected node
            for node in selectedNodes:
                node.translate(delta.x(), delta.y())

        elif self._manipulationMode == MANIP_MODE_ZOOM:

            # How much
            delta = event.pos() - self._lastMousePos
            zoomFactor = 1.0
            if delta.x() > 0:
                zoomFactor = 1.0 + delta.x() / 100.0
            else:
                zoomFactor = 1.0 / (1.0 + abs(delta.x()) / 100.0)

            # Limit zoom to 3x
            if self._lastTransform.m22() * zoomFactor >= 2.0:
                return

            # Reset to when we mouse pressed
            self.setSceneRect(self._lastSceneRect)
            self.setTransform(self._lastTransform)

            # Center scene around mouse down
            rect = self.sceneRect()
            rect.translate(self._lastOffsetFromSceneCenter)
            self.setSceneRect(rect)

            # Zoom in (QGraphicsView auto-centers!)
            self.scale(zoomFactor, zoomFactor)

            newSceneCenter = self.sceneRect().center()
            newScenePos = self.mapToScene(self._lastMousePos)
            newOffsetFromSceneCenter = newScenePos - newSceneCenter

            # Put mouse down back where is was on screen
            rect = self.sceneRect()
            rect.translate(-1 * newOffsetFromSceneCenter)
            self.setSceneRect(rect)

            # Call udpate to redraw background
            self.update()

        else:
            super(GraphView, self).mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        if self._manipulationMode == MANIP_MODE_SELECT:

            # If users simply clicks in the empty space, clear selection.
            if self.mapToScene(event.pos()) == self._selectionRect.pos():
                self.clearSelection(emitSignal=False)

            self._selectionRect.destroy()
            self._selectionRect = None
            self._manipulationMode = MANIP_MODE_NONE

            selection = self.getSelectedNodes()

            deselectedNodes = []
            selectedNodes = []

            for node in self._mouseDownSelection:
                if node not in selection:
                    deselectedNodes.append(node)

            for node in selection:
                if node not in self._mouseDownSelection:
                    selectedNodes.append(node)

            if selectedNodes != deselectedNodes:
                self.selectionChanged.emit(deselectedNodes, selectedNodes)

            self.endNodeSelection.emit()

        elif self._manipulationMode == MANIP_MODE_PAN:
            self.setCursor(QtCore.Qt.ArrowCursor)
            self._manipulationMode = MANIP_MODE_NONE

        elif self._manipulationMode == MANIP_MODE_ZOOM:
            self.setCursor(QtCore.Qt.ArrowCursor)
            self._manipulationMode = MANIP_MODE_NONE
            #self.setTransformationAnchor(self._lastAnchor)

        else:
            super(GraphView, self).mouseReleaseEvent(event)

    def wheelEvent(self, event):

        zoomFactor = 1.0 + event.delta() * self._mouseWheelZoomRate

        transform = self.transform()
        # Limit zoom to 3x
        if transform.m22() * zoomFactor >= 2.0:
            return

        sceneCenter = self.sceneRect().center()
        scenePoint = self.mapToScene(event.pos())
        posFromSceneCenter = scenePoint - sceneCenter

        rect = self.sceneRect()
        rect.translate(posFromSceneCenter)
        self.setSceneRect(rect)

        # Zoom in (QGraphicsView auto-centers!)
        self.scale(zoomFactor, zoomFactor)

        # Translate scene back to align original mouse press
        sceneCenter = self.sceneRect().center()
        scenePoint = self.mapToScene(event.pos())
        posFromSceneCenter = scenePoint - sceneCenter

        rect = self.sceneRect()
        posFromSceneCenter *= -1.0
        rect.translate(posFromSceneCenter)
        self.setSceneRect(rect)

        # Call udpate to redraw background
        self.update()

    ################################################
    ## Painting

    def drawBackground(self, painter, rect):

        oldTransform = painter.transform()
        painter.fillRect(rect, self._backgroundColor)

        left = int(rect.left()) - (int(rect.left()) % self._gridSizeFine)
        top = int(rect.top()) - (int(rect.top()) % self._gridSizeFine)

        # Draw horizontal fine lines
        gridLines = []
        painter.setPen(self._gridPenS)
        y = float(top)
        while y < float(rect.bottom()):
            gridLines.append(QtCore.QLineF(rect.left(), y, rect.right(), y))
            y += self._gridSizeFine
        painter.drawLines(gridLines)

        # Draw vertical fine lines
        gridLines = []
        painter.setPen(self._gridPenS)
        x = float(left)
        while x < float(rect.right()):
            gridLines.append(QtCore.QLineF(x, rect.top(), x, rect.bottom()))
            x += self._gridSizeFine
        painter.drawLines(gridLines)

        # Draw thick grid
        left = int(rect.left()) - (int(rect.left()) % self._gridSizeCourse)
        top = int(rect.top()) - (int(rect.top()) % self._gridSizeCourse)

        # Draw vertical thick lines
        gridLines = []
        painter.setPen(self._gridPenL)
        x = left
        while x < rect.right():
            gridLines.append(QtCore.QLineF(x, rect.top(), x, rect.bottom()))
            x += self._gridSizeCourse
        painter.drawLines(gridLines)

        # Draw horizontal thick lines
        gridLines = []
        painter.setPen(self._gridPenL)
        y = top
        while y < rect.bottom():
            gridLines.append(QtCore.QLineF(rect.left(), y, rect.right(), y))
            y += self._gridSizeCourse
        painter.drawLines(gridLines)

        return super(GraphView, self).drawBackground(painter, rect)
Exemplo n.º 7
0
class Node(QtWidgets.QGraphicsWidget):

    nameChanged = QtCore.Signal(str, str)

    __defaultColor = QtGui.QColor(154, 205, 50, 255)
    __defaultUnselectedColor = QtGui.QColor(25, 25, 25)
    __defaultSelectedColor = QtGui.QColor(255, 255, 255, 255)

    __defaultUnselectedPen = QtGui.QPen(__defaultUnselectedColor, 1.6)
    __defaultSelectedPen = QtGui.QPen(__defaultSelectedColor, 1.6)
    __defaultLinePen = QtGui.QPen(QtGui.QColor(25, 25, 25, 255), 1.25)

    def __init__(self, graph, name):
        super(Node, self).__init__()

        self.__name = name
        self.__graph = graph
        self.__color = self.__defaultColor
        self.__unselectedColor = self.__defaultUnselectedColor
        self.__selectedColor = self.__defaultSelectedColor

        self.__unselectedPen = QtGui.QPen(self.__defaultUnselectedPen)
        self.__selectedPen = QtGui.QPen(self.__defaultSelectedPen)
        self.__linePen = QtGui.QPen(self.__defaultLinePen)

        self.setMinimumWidth(60)
        self.setMinimumHeight(20)
        self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding))

        layout = QtWidgets.QGraphicsLinearLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.setOrientation(QtCore.Qt.Vertical)
        self.setLayout(layout)

        self.__headerItem = NodeHeader(self.__name, self)
        layout.addItem(self.__headerItem)
        layout.setAlignment(self.__headerItem, QtCore.Qt.AlignCenter | QtCore.Qt.AlignTop)

        self.__ports = []
        self.__inputPortsHolder = PortList(self)
        self.__ioPortsHolder = PortList(self)
        self.__outputPortsHolder = PortList(self)

        layout.addItem(self.__inputPortsHolder)
        layout.addItem(self.__ioPortsHolder)
        layout.addItem(self.__outputPortsHolder)

        self.__selected = False
        self.__dragging = False

    # =====
    # Name
    # =====
    def getName(self):
        return self.__name

    def setName(self, name):
        if name != self.__name:
            origName = self.__name
            self.__name = name
            self.__headerItem.setText(self.__name)

            # Emit an event, so that the graph can update itsself.
            self.nameChanged.emit(origName, name)

            # Update the node so that the size is computed.
            self.adjustSize()

    # =======
    # Colors
    # =======
    def getColor(self):
        return self.__color

    def setColor(self, color):
        self.__color = color
        self.update()


    def getUnselectedColor(self):
        return self.__unselectedColor

    def setUnselectedColor(self, color):
        self.__unselectedColor = color
        self.__unselectedPen.setColor(self.__unselectedColor)
        self.update()


    def getSelectedColor(self):
        return self.__selectedColor

    def setSelectedColor(self, color):
        self.__selectedColor = color
        self.__selectedPen.setColor(self.__selectedColor)
        self.update()

    # =============
    # Misc Methods
    # =============
    def getGraph(self):
        return self.__graph


    def getHeader(self):
        return self.__headerItem


    # ==========
    # Selection
    # ==========
    def isSelected(self):
        return self.__selected

    def setSelected(self, selected=True):
        self.__selected = selected
        self.setZValue(20.0)
        self.update()


    #########################
    ## Graph Pos

    def getGraphPos(self):
        transform = self.transform()
        size = self.size()
        return QtCore.QPointF(transform.dx()+(size.width()*0.5), transform.dy()+(size.height()*0.5))


    def setGraphPos(self, graphPos):
        self.prepareConnectionGeometryChange()
        size = self.size()
        self.setTransform(QtGui.QTransform.fromTranslate(graphPos.x()-(size.width()*0.5), graphPos.y()-(size.height()*0.5)), False)


    def translate(self, x, y):
        self.prepareConnectionGeometryChange()
        currPos = self.pos()
        super(Node, self).setPos(currPos.x() + x, currPos.y() + y)


    # Prior to moving the node, we need to tell the connections to prepare for a geometry change.
    # This method must be called preior to moving a node.
    def prepareConnectionGeometryChange(self):
        for port in self.__ports:
            if port.inCircle():
                for connection in port.inCircle().getConnections():
                    connection.prepareGeometryChange()
            if port.outCircle():
                for connection in port.outCircle().getConnections():
                    connection.prepareGeometryChange()

    #########################
    ## Ports

    def addPort(self, port):
        if isinstance(port, InputPort):
            self.__inputPortsHolder.addPort(port, QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
        elif isinstance(port, OutputPort):
            self.__outputPortsHolder.addPort(port, QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
        else:
            self.__ioPortsHolder.addPort(port, QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
        self.__ports.append(port)

        self.adjustSize()
        return port


    def getPort(self, name):
        for port in self.__ports:
            if port.getName() == name:
                return port
        return None

    def getInputPort(self, name):
        for port in self.__ports:
            if port.getName() == name and isinstance(port, (InputPort, IOPort)):
                return port
        return None

    def getOutputPort(self, name):
        for port in self.__ports:
            if port.getName() == name and isinstance(port, (OutputPort, IOPort)):
                return port
        return None


    def paint(self, painter, option, widget):
        rect = self.windowFrameRect()
        painter.setBrush(self.__color)

        painter.setPen(QtGui.QPen(QtGui.QColor(0, 0, 0, 0), 0))

        roundingY = 10
        roundingX = rect.height() / rect.width() * roundingY

        painter.drawRoundRect(rect, roundingX, roundingY)

        # Title BG
        titleHeight = self.__headerItem.size().height() - 3

        painter.setBrush(self.__color.darker(125))
        roundingY = rect.width() * roundingX / titleHeight
        painter.drawRoundRect(0, 0, rect.width(), titleHeight, roundingX, roundingY)
        painter.drawRect(0, titleHeight * 0.5 + 2, rect.width(), titleHeight * 0.5)

        # painter.setPen(self.__linePen)
        # painter.drawLine(QtCore.QPoint(0, titleHeight), QtCore.QPoint(rect.width(), titleHeight))

        painter.setBrush(QtGui.QColor(0, 0, 0, 0))
        if self.__selected:
            painter.setPen(self.__selectedPen)
        else:
            painter.setPen(self.__unselectedPen)

        roundingY = 10
        roundingX = rect.height() / rect.width() * roundingY

        painter.drawRoundRect(rect, roundingX, roundingY)


    #########################
    ## Events

    def mousePressEvent(self, event):
        if event.button() is QtCore.Qt.MouseButton.LeftButton:

            modifiers = event.modifiers()
            if modifiers == QtCore.Qt.ControlModifier:
                if not self.isSelected():
                    self.__graph.selectNode(self, clearSelection=False)
                else:
                    self.__graph.deselectNode(self)

            elif modifiers == QtCore.Qt.ShiftModifier:
                if not self.isSelected():
                    self.__graph.selectNode(self, clearSelection=False)
            else:
                if not self.isSelected():
                    self.__graph.selectNode(self, clearSelection=True)

                    # Push all nodes back 1 level in z depth to bring selected
                    # node to front
                    for node in [x for x in self.__graph.getNodes().values()]:
                        if node == self:
                            continue

                        if node.zValue() != 0.0:
                            node.setZValue(node.zValue() - 1)

                self.__dragging = True
                self._mouseDownPoint = self.mapToScene(event.pos())
                self._mouseDelta = self._mouseDownPoint - self.getGraphPos()
                self._lastDragPoint = self._mouseDownPoint
                self._nodesMoved = False

        else:
            super(Node, self).mousePressEvent(event)


    def mouseMoveEvent(self, event):
        if self.__dragging:
            newPos = self.mapToScene(event.pos())

            graph = self.getGraph()
            if graph.getSnapToGrid() is True:
                gridSize = graph.getGridSize()

                newNodePos = newPos - self._mouseDelta

                snapPosX = math.floor(newNodePos.x() / gridSize) * gridSize
                snapPosY = math.floor(newNodePos.y() / gridSize) * gridSize
                snapPos = QtCore.QPointF(snapPosX, snapPosY)

                newPosOffset = snapPos - newNodePos

                newPos = newPos + newPosOffset

            delta = newPos - self._lastDragPoint
            self.__graph.moveSelectedNodes(delta)
            self._lastDragPoint = newPos
            self._nodesMoved = True
        else:
            super(Node, self).mouseMoveEvent(event)


    def mouseReleaseEvent(self, event):
        if self.__dragging:
            if self._nodesMoved:

                newPos = self.mapToScene(event.pos())

                delta = newPos - self._mouseDownPoint
                self.__graph.endMoveSelectedNodes(delta)

            self.setCursor(QtCore.Qt.ArrowCursor)
            self.__dragging = False
        else:
            super(Node, self).mouseReleaseEvent(event)


    #########################
    ## shut down

    def disconnectAllPorts(self):
        # gather all the connections into a list, and then remove them from the graph.
        # This is because we can't remove connections from ports while
        # iterating over the set.
        connections = []

        for port in self.__ports:
            if port.inCircle():
                for connection in port.inCircle().getConnections():
                    connections.append(connection)
            if port.outCircle():
                for connection in port.outCircle().getConnections():
                    connections.append(connection)

        for connection in connections:
            self.__graph.removeConnection(connection)
Exemplo n.º 8
0
class KGraphView(GraphView):

    beginCopyData = QtCore.Signal()
    endCopyData = QtCore.Signal()

    beginPasteData = QtCore.Signal()
    endPasteData = QtCore.Signal()

    _clipboardData = None

    def __init__(self, parent=None):
        super(KGraphView, self).__init__(parent)
        self.__rig = None
        self.setAcceptDrops(True)

    def getRig(self):
        return self.__rig

    # ======
    # Graph
    # ======
    def displayGraph(self, rig):
        self.reset()

        self.__rig = rig

        guideComponents = self.__rig.getChildrenByType('Component')

        for component in guideComponents:
            node = KNode(self, component)
            self.addNode(node)

        for component in guideComponents:
            for i in range(component.getNumInputs()):
                componentInput = component.getInputByIndex(i)
                if componentInput.isConnected():
                    componentOutput = componentInput.getConnection()

                    self.connectPorts(
                        srcNode=componentOutput.getParent().getDecoratedName(),
                        outputName=componentOutput.getName(),
                        tgtNode=component.getDecoratedName(),
                        inputName=componentInput.getName())

        # Get backdrops from meta data
        metaData = self.__rig.getMetaData()
        if 'backdrops' in metaData:
            for backdrop in metaData['backdrops']:
                backdropNode = KBackdrop(self,
                                         backdrop.get('name', 'Backdrop'))
                self.addNode(backdropNode)
                backdropNode.setData(backdrop)

        self.frameAllNodes()

    def addConnection(self, connection, emitSignal=True):

        result = super(KGraphView, self).addConnection(connection,
                                                       emitSignal=emitSignal)

        # Indicate that this is an indexed connection.
        outPort = connection.getSrcPortCircle().getPort()
        inPort = connection.getDstPortCircle().getPort()
        if outPort is not None and inPort is not None and outPort.getDataType(
        ) != inPort.getDataType():
            if outPort.getDataType().startswith(inPort.getDataType(
            )) and outPort.getDataType().endswith('[]'):
                connection.setPenStyle(QtCore.Qt.DashDotLine)
                connection.setPenWidth(2.5)

        return connection

    def getNodesOfType(self, nodeType):
        """Gets all the nodes of the specified type.

        Arguments:
        nodeType -- String, class name to search nodes for.

        Return:
        list, nodes that are of the specified type.

        """

        graphNodes = self.getNodes()

        return [
            graphNodes[x] for x in graphNodes
            if type(graphNodes[x]).__name__ == nodeType
        ]

    # =======
    # Events
    # =======
    def mousePressEvent(self, event):

        modifiers = QtWidgets.QApplication.keyboardModifiers()

        if event.button() == QtCore.Qt.MouseButton.RightButton:

            zoom_with_alt_rmb = self.window().preferences.getPreferenceValue(
                'zoom_with_alt_rmb')
            if zoom_with_alt_rmb and modifiers == QtCore.Qt.AltModifier:
                self._manipulationMode = MANIP_MODE_ZOOM
                self.setCursor(QtCore.Qt.SizeHorCursor)
                self._lastMousePos = event.pos()
                self._lastTransform = QtGui.QTransform(self.transform())
                self._lastSceneRect = self.sceneRect()
                self._lastSceneCenter = self._lastSceneRect.center()
                self._lastScenePos = self.mapToScene(event.pos())
                self._lastOffsetFromSceneCenter = self._lastScenePos - self._lastSceneCenter
                return

            def graphItemAt(item):
                if isinstance(item, KNode):
                    return item
                if isinstance(item, Connection):
                    return item
                elif item is not None:
                    return graphItemAt(item.parentItem())
                return None

            graphicItem = graphItemAt(self.itemAt(event.pos()))
            pos = self.mapToScene(event.pos())

            if graphicItem is None:

                contextMenu = QtWidgets.QMenu(self.getGraphViewWidget())
                contextMenu.setObjectName('rightClickContextMenu')
                contextMenu.setMinimumWidth(150)

                if self.getClipboardData() is not None:

                    def pasteSettings():
                        self.pasteSettings(pos)

                    def pasteSettingsMirrored():
                        self.pasteSettings(pos, mirrored=True)

                    contextMenu.addAction("Paste").triggered.connect(
                        pasteSettings)
                    contextMenu.addAction("Paste Mirrored").triggered.connect(
                        pasteSettingsMirrored)
                    contextMenu.addSeparator()

                graphViewWidget = self.getGraphViewWidget()
                contextMenu.addAction("Add Backdrop").triggered.connect(
                    graphViewWidget.addBackdrop)
                contextMenu.popup(event.globalPos())

            if isinstance(graphicItem, KNode):
                self.selectNode(graphicItem,
                                clearSelection=True,
                                emitSignal=True)

                contextMenu = QtWidgets.QMenu(self.getGraphViewWidget())
                contextMenu.setObjectName('rightClickContextMenu')
                contextMenu.setMinimumWidth(150)

                def copySettings():
                    self.copySettings(pos)

                contextMenu.addAction("Copy").triggered.connect(copySettings)

                if self.getClipboardData() is not None:

                    def pasteSettings():
                        # Paste the settings, not modifying the location, because that will be used to determine symmetry.
                        pasteData = self.getClipboardData()['components'][0]
                        pasteData.pop('graphPos', None)

                        graphicItem.getComponent().pasteData(pasteData,
                                                             setLocation=False)

                    contextMenu.addSeparator()
                    contextMenu.addAction("Paste Data").triggered.connect(
                        pasteSettings)

                contextMenu.popup(event.globalPos())

            elif isinstance(graphicItem, Connection):

                outPort = graphicItem.getSrcPortCircle().getPort()
                inPort = graphicItem.getDstPortCircle().getPort()
                if outPort.getDataType() != inPort.getDataType():

                    if outPort.getDataType().startswith(inPort.getDataType(
                    )) and outPort.getDataType().endswith('[]'):

                        globalPos = event.globalPos()
                        contextMenu = QtWidgets.QMenu(
                            self.getGraphViewWidget())
                        contextMenu.setObjectName('rightClickContextMenu')
                        contextMenu.setMinimumWidth(150)

                        def editIndex():
                            componentInput = graphicItem.getDstPortCircle(
                            ).getPort().getComponentInput()
                            EditIndexWidget(componentInput,
                                            pos=globalPos,
                                            parent=self.getGraphViewWidget())

                        contextMenu.addAction("EditIndex").triggered.connect(
                            editIndex)
                        contextMenu.popup(globalPos)

        elif event.button(
        ) is QtCore.Qt.MouseButton.LeftButton and self.itemAt(
                event.pos()) is None:
            self.beginNodeSelection.emit()
            self._manipulationMode = MANIP_MODE_SELECT
            self._mouseDownSelection = copy.copy(self.getSelectedNodes())
            self._selectionRect = SelectionRect(graph=self,
                                                mouseDownPos=self.mapToScene(
                                                    event.pos()))

        elif event.button() is QtCore.Qt.MouseButton.MiddleButton:

            pan_with_alt = self.window().preferences.getPreferenceValue(
                'pan_with_alt')
            if pan_with_alt is True and modifiers != QtCore.Qt.AltModifier:
                return

            self.setCursor(QtCore.Qt.OpenHandCursor)
            self._manipulationMode = MANIP_MODE_PAN
            self._lastPanPoint = self.mapToScene(event.pos())

        else:
            super(GraphView, self).mousePressEvent(event)

    def dragEnterEvent(self, event):
        textParts = event.mimeData().text().split(':')
        if textParts[0] == 'KrakenComponent':
            event.accept()
        else:
            event.setDropAction(QtCore.Qt.IgnoreAction)
            super(GraphView, self).dragEnterEvent(event)

    def dragMoveEvent(self, event):
        super(GraphView, self).dragMoveEvent(event)
        event.accept()

    def dropEvent(self, event):
        textParts = event.mimeData().text().split(':')
        if textParts[0] == 'KrakenComponent':
            componentClassName = textParts[1]

            # Add a component to the rig placed at the given position.
            dropPosition = self.mapToScene(event.pos())

            # construct the node and add it to the graph.
            krakenSystem = KrakenSystem.getInstance()
            componentClass = krakenSystem.getComponentClass(componentClassName)
            component = componentClass(parent=self.getRig())
            component.setGraphPos(Vec2(dropPosition.x(), dropPosition.y()))
            node = KNode(self, component)
            self.addNode(node)

            self.selectNode(node, clearSelection=True, emitSignal=False)

            event.acceptProposedAction()
        else:
            super(GraphView, self).dropEvent(event)

    def wheelEvent(self, event):

        zoom_mouse_scroll = self.window().preferences.getPreferenceValue(
            'zoom_mouse_scroll')
        if zoom_mouse_scroll is True:
            super(KGraphView, self).wheelEvent(event)

    # =============
    # Copy / Paste
    # =============
    def getClipboardData(self):
        return self.__class__._clipboardData

    def copySettings(self, pos):
        clipboardData = {}

        copiedComponents = []
        nodes = self.getSelectedNodes()
        for node in nodes:
            copiedComponents.append(node.getComponent())

        componentsJson = []
        connectionsJson = []
        for component in copiedComponents:
            componentsJson.append(component.copyData())

            for i in range(component.getNumInputs()):
                componentInput = component.getInputByIndex(i)
                if componentInput.isConnected():
                    componentOutput = componentInput.getConnection()
                    connectionJson = {
                        'source':
                        componentOutput.getParent().getDecoratedName() + '.' +
                        componentOutput.getName(),
                        'target':
                        component.getDecoratedName() + '.' +
                        componentInput.getName()
                    }

                    connectionsJson.append(connectionJson)

        clipboardData = {
            'components': componentsJson,
            'connections': connectionsJson,
            'copyPos': pos
        }

        self.__class__._clipboardData = clipboardData

    def pasteSettings(self,
                      pos,
                      mirrored=False,
                      createConnectionsToExistingNodes=True):

        clipboardData = self.__class__._clipboardData

        krakenSystem = KrakenSystem.getInstance()
        delta = pos - clipboardData['copyPos']
        self.clearSelection()
        pastedComponents = {}
        nameMapping = {}

        for componentData in clipboardData['components']:
            componentClass = krakenSystem.getComponentClass(
                componentData['class'])
            component = componentClass(parent=self.__rig)
            decoratedName = componentData[
                'name'] + component.getNameDecoration()
            nameMapping[decoratedName] = decoratedName
            if mirrored:
                config = Config.getInstance()
                mirrorMap = config.getNameTemplate()['mirrorMap']
                component.setLocation(mirrorMap[componentData['location']])
                nameMapping[decoratedName] = componentData[
                    'name'] + component.getNameDecoration()
                component.pasteData(componentData, setLocation=False)
            else:
                component.pasteData(componentData, setLocation=True)

            graphPos = component.getGraphPos()
            component.setGraphPos(
                Vec2(graphPos.x + delta.x(), graphPos.y + delta.y()))

            node = KNode(self, component)
            self.addNode(node)
            self.selectNode(node, False)

            # save a dict of the nodes using the orignal names
            pastedComponents[nameMapping[decoratedName]] = component

        # Create Connections
        for connectionData in clipboardData['connections']:
            sourceComponentDecoratedName, outputName = connectionData[
                'source'].split('.')
            targetComponentDecoratedName, inputName = connectionData[
                'target'].split('.')

            sourceComponent = None

            # The connection is either between nodes that were pasted, or from pasted nodes
            # to unpasted nodes. We first check that the source component is in the pasted group
            # else use the node in the graph.
            if sourceComponentDecoratedName in nameMapping:
                sourceComponent = pastedComponents[
                    nameMapping[sourceComponentDecoratedName]]
            else:
                if not createConnectionsToExistingNodes:
                    continue

                # When we support copying/pasting between rigs, then we may not find the source
                # node in the target rig.
                if not self.hasNode(sourceComponentDecoratedName):
                    continue
                node = self.getNode(sourceComponentDecoratedName)
                sourceComponent = node.getComponent()

            targetComponentDecoratedName = nameMapping[
                targetComponentDecoratedName]
            targetComponent = pastedComponents[targetComponentDecoratedName]

            outputPort = sourceComponent.getOutputByName(outputName)
            inputPort = targetComponent.getInputByName(inputName)

            inputPort.setConnection(outputPort)
            self.connectPorts(srcNode=sourceComponent.getDecoratedName(),
                              outputName=outputPort.getName(),
                              tgtNode=targetComponent.getDecoratedName(),
                              inputName=inputPort.getName())
Exemplo n.º 9
0
class KBackdrop(QtWidgets.QGraphicsWidget):

    nameChanged = QtCore.Signal(str, str)
    sizeChanged = QtCore.Signal(float)

    __defaultColor = QtGui.QColor(65, 120, 122, 255)
    __defaultUnselectedColor = QtGui.QColor(__defaultColor.darker(125))
    __defaultSelectedColor = QtGui.QColor(__defaultColor.lighter(175))
    __defaultHoverColor = QtGui.QColor(__defaultColor.lighter(110))

    __defaultUnselectedPen = QtGui.QPen(__defaultUnselectedColor, 1.6)
    __defaultSelectedPen = QtGui.QPen(__defaultSelectedColor, 1.6)
    __defaultHoveredPen = QtGui.QPen(__defaultHoverColor, 1.6)
    __defaultLinePen = QtGui.QPen(QtGui.QColor(25, 25, 25, 255), 1.25)

    __resizeDistance = 16.0
    __setCustomCursor = False
    __hoveredOver = False

    def __init__(self, graph, name):
        super(KBackdrop, self).__init__()
        self.setAcceptHoverEvents(True)

        self.__name = name
        self.__comment = None

        self.__graph = graph
        self.__color = self.__defaultColor
        self.__color.setAlpha(25)
        self.__unselectedColor = self.__defaultUnselectedColor
        self.__selectedColor = self.__defaultSelectedColor
        self.__hoverColor = self.__defaultHoverColor

        self.__unselectedPen = QtGui.QPen(self.__defaultUnselectedPen)
        self.__selectedPen = QtGui.QPen(self.__defaultSelectedPen)
        self.__hoveredPen = QtGui.QPen(self.__defaultHoveredPen)
        self.__linePen = QtGui.QPen(self.__defaultLinePen)

        self.__inspectorWidget = None

        self.setMinimumWidth(120)
        self.setMinimumHeight(80)
        self.setZValue(-100)

        # Set defaults for interactions
        self.__selected = False
        self.__dragging = False
        self.__resizing = False
        self.__resizeCorner = -1
        self.__resizeEdge = -1

        self.createLayout()
        self.createConnections()

        # Initialize the comment with the name
        self.setComment(name)

    def createLayout(self):

        layout = QtWidgets.QGraphicsLinearLayout()
        layout.setContentsMargins(5, 0, 5, 7)
        layout.setSpacing(7)
        layout.setOrientation(QtCore.Qt.Vertical)
        self.setLayout(layout)

        self.__headerItem = KBackdropHeader(self.__name, self)
        self.__titleWidget = self.__headerItem.getTitleWidget()
        layout.addItem(self.__headerItem)
        layout.setAlignment(self.__headerItem,
                            QtCore.Qt.AlignCenter | QtCore.Qt.AlignTop)
        layout.addStretch(1)

    def createConnections(self):

        self.sizeChanged.connect(self.__titleWidget.nodeResized)

    def getName(self):
        return self.__name

    def setName(self, name):
        if name != self.__name:
            origName = self.__name
            self.__name = name
            # self.__headerItem.setText(self.__name)

            # Emit an event, so that the graph can update itsself.
            self.nameChanged.emit(origName, name)

            # Update the node so that the size is computed.
            self.adjustSize()

    def getComment(self):
        return self.__comment

    def setComment(self, comment):
        self.__comment = comment
        self.__headerItem.setText(comment)

        # Resize the width of the backdrop based on title width
        titleWidget = self.__headerItem.getTitleWidget()
        titleWidth = titleWidget.textSize().width()
        self.resize(titleWidth, self.size().height())

        # Update the node so that the size is computed.
        self.adjustSize()

    def getColor(self):
        color = QtGui.QColor(self.__color)
        color.setAlpha(255)
        return color

    def setColor(self, color):
        self.__color = color
        self.__color.setAlpha(25)
        self.update()

    def getUnselectedColor(self):
        return self.__unselectedColor

    def setUnselectedColor(self, color):
        self.__unselectedColor = color
        self.__unselectedPen.setColor(self.__unselectedColor)
        self.update()

    def getSelectedColor(self):
        return self.__selectedColor

    def setSelectedColor(self, color):
        self.__selectedColor = color
        self.__selectedPen.setColor(self.__selectedColor)
        self.update()

    def getHoveredColor(self):
        return self.__hoverColor

    def setHoveredColor(self, color):
        self.__hoverColor = color
        self.__hoveredPen.setColor(self.__hoverColor)
        self.update()

    def getGraph(self):
        return self.__graph

    def getHeader(self):
        return self.__headerItem

    # ==========
    # Selection
    # ==========
    def isSelected(self):
        return self.__selected

    def setSelected(self, selected=True):
        self.__selected = selected
        self.update()

    # ==========
    # Graph Pos
    # ==========
    def getGraphPos(self):
        transform = self.transform()
        size = self.size()
        return QtCore.QPointF(transform.dx() + (size.width() * 0.5),
                              transform.dy() + (size.height() * 0.5))

    def setGraphPos(self, graphPos):
        size = self.size()
        self.setTransform(
            QtGui.QTransform.fromTranslate(
                graphPos.x() - (size.width() * 0.5),
                graphPos.y() - (size.height() * 0.5)), False)

    def translate(self, x, y):
        super(KBackdrop, self).translate(x, y)

    def paint(self, painter, option, widget):
        rect = self.windowFrameRect()
        painter.setBrush(self.__color)

        painter.setPen(QtGui.QPen(QtGui.QColor(0, 0, 0, 0), 0))

        roundingY = 20.0 / (rect.height() / 80.0)
        roundingX = rect.height() / rect.width() * roundingY

        painter.drawRoundRect(rect,
                              roundingX,
                              roundingY,
                              mode=QtCore.Qt.AbsoluteSize)

        # Title BG
        titleHeight = self.__headerItem.size().height() - 3

        darkerColor = self.__color.darker(125)
        darkerColor.setAlpha(255)
        painter.setBrush(darkerColor)
        roundingYHeader = rect.width() * roundingX / titleHeight
        painter.drawRoundRect(0, 0, rect.width(), titleHeight, roundingX,
                              roundingYHeader)
        painter.drawRect(0, titleHeight * 0.5 + 2, rect.width(),
                         titleHeight * 0.5)

        painter.setBrush(QtGui.QColor(0, 0, 0, 0))
        if self.__selected:
            painter.setPen(self.__selectedPen)
        elif self.__hoveredOver:
            painter.setPen(self.__hoveredPen)
        else:
            painter.setPen(self.__unselectedPen)

        painter.drawRoundRect(rect,
                              roundingX,
                              roundingY,
                              mode=QtCore.Qt.AbsoluteSize)

        super(KBackdrop, self).paint(painter, option, widget)

    # =======
    # Events
    # =======
    def mousePressEvent(self, event):
        if event.button() is QtCore.Qt.MouseButton.LeftButton:

            resizeCorner = self.getCorner(event.pos())
            resizeEdge = self.getEdge(event.pos())
            if resizeCorner != -1 and self.isSelected():
                self.__resizing = True
                self.__resizeCorner = resizeCorner
                self._resizedBackdrop = False
            elif resizeEdge != -1 and self.isSelected():
                self.__resizing = True
                self.__resizeEdge = resizeEdge
                self._resizedBackdrop = False
            else:
                modifiers = event.modifiers()
                if modifiers == QtCore.Qt.ControlModifier:
                    if not self.isSelected():
                        self.__graph.selectNode(self, clearSelection=False)
                    else:
                        self.__graph.deselectNode(self)

                elif modifiers == QtCore.Qt.ShiftModifier:
                    if not self.isSelected():
                        self.__graph.selectNode(self, clearSelection=False)
                else:
                    if not self.isSelected():
                        self.__graph.selectNode(self, clearSelection=True)

                    if self.__headerItem.contains(event.pos()):
                        self.setCursor(QtCore.Qt.ClosedHandCursor)
                        self.__dragging = True
                        self._nodesMoved = False

            self._mouseDownPoint = self.mapToScene(event.pos())
            self._mouseDelta = self._mouseDownPoint - self.getGraphPos()
            self._lastDragPoint = self._mouseDownPoint

            self._initPos = self.pos()
            self._initScenePos = self.mapToScene(self.pos())
            self._initBoundingRect = self.boundingRect()
            self._initSceneBoundingRect = self.sceneBoundingRect()

        else:
            super(KBackdrop, self).mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if self.__dragging:
            newPos = self.mapToScene(event.pos())

            graph = self.getGraph()
            if graph.getSnapToGrid() is True:
                gridSize = graph.getGridSize()

                newNodePos = newPos - self._mouseDelta

                snapPosX = math.floor(newNodePos.x() / gridSize) * gridSize
                snapPosY = math.floor(newNodePos.y() / gridSize) * gridSize
                snapPos = QtCore.QPointF(snapPosX, snapPosY)

                newPosOffset = snapPos - newNodePos

                newPos = newPos + newPosOffset

            delta = newPos - self._lastDragPoint
            self.__graph.moveSelectedNodes(delta)
            self._lastDragPoint = newPos
            self._nodesMoved = True

        elif self.__resizing:

            newPos = self.mapToScene(event.pos())
            delta = newPos - self._mouseDownPoint
            self._resizedBackdrop = True

            newPosX = 0
            newPosY = 0
            newWidth = self._initBoundingRect.width()
            newHeight = self._initBoundingRect.height()

            if self.__resizeCorner == 0:

                newWidth = self._initBoundingRect.width() + (delta.x() * -1.0)
                newHeight = self._initBoundingRect.height() + (delta.y() *
                                                               -1.0)

                if newWidth <= self.minimumWidth():
                    newWidth = self.minimumWidth()
                else:
                    newPosX = self._initPos.x() + delta.x()

                if newHeight <= self.minimumHeight():
                    newHeight = self.minimumHeight()
                else:
                    newPosY = self._initPos.y() + delta.y()

            elif self.__resizeCorner == 1:

                newWidth = self._initBoundingRect.width() + delta.x()
                newHeight = self._initBoundingRect.height() + (delta.y() *
                                                               -1.0)

                if newWidth <= self.minimumWidth():
                    newWidth = self.minimumWidth()
                else:
                    newPosX = self._initPos.x()

                if newHeight <= self.minimumHeight():
                    newHeight = self.minimumHeight()
                else:
                    newPosY = self._initPos.y() + delta.y()

            elif self.__resizeCorner == 2:

                newWidth = self._initBoundingRect.width() + (delta.x() * -1.0)
                newHeight = self._initBoundingRect.height() + delta.y()

                if newWidth <= self.minimumWidth():
                    newWidth = self.minimumWidth()
                else:
                    newPosX = self._initPos.x() + delta.x()

                if newHeight <= self.minimumHeight():
                    newHeight = self.minimumHeight()
                else:
                    newPosY = self._initPos.y()

            elif self.__resizeCorner == 3:
                newPosX = self._initPos.x()
                newPosY = self._initPos.y()
                newWidth = self._initBoundingRect.width() + delta.x()
                newHeight = self._initBoundingRect.height() + delta.y()

                if newWidth <= self.minimumWidth():
                    newWidth = self.minimumWidth()

                if newHeight <= self.minimumHeight():
                    newHeight = self.minimumHeight()

            elif self.__resizeEdge == 0:
                pass

            elif self.__resizeEdge == 1:
                newWidth = self._initBoundingRect.width() + delta.x()
                newHeight = self._initBoundingRect.height()

                newPosY = self._initPos.y()

                if newWidth <= self.minimumWidth():
                    newWidth = self.minimumWidth()
                else:
                    newPosX = self._initPos.x()

            elif self.__resizeEdge == 2:
                newWidth = self._initBoundingRect.width()
                newHeight = self._initBoundingRect.height() + delta.y()

                newPosX = self._initPos.x()

                if newHeight <= self.minimumHeight():
                    newHeight = self.minimumHeight()
                else:
                    newPosY = self._initPos.y()

            elif self.__resizeEdge == 3:
                newWidth = self._initBoundingRect.width() + (delta.x() * -1.0)
                newHeight = self._initBoundingRect.height()

                newPosY = self._initPos.y()

                if newWidth <= self.minimumWidth():
                    newWidth = self.minimumWidth()
                else:
                    newPosX = self._initPos.x() + delta.x()

            self.setPos(newPosX, newPosY)
            self.resize(newWidth, newHeight)

            self.sizeChanged.emit(newWidth)

            self.prepareGeometryChange()

        else:
            super(KBackdrop, self).mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        if self.__dragging:
            if self._nodesMoved:

                newPos = self.mapToScene(event.pos())

                delta = newPos - self._mouseDownPoint
                self.__graph.endMoveSelectedNodes(delta)

            self.setCursor(QtCore.Qt.ArrowCursor)
            self.__dragging = False

        elif self.__resizing:

            self.setCursor(QtCore.Qt.ArrowCursor)
            self.__resizing = False
            self.__resizeCorner = -1

        else:
            super(KBackdrop, self).mouseReleaseEvent(event)

    def mouseDoubleClickEvent(self, event):
        if self.__inspectorWidget is None:
            parentWidget = self.getGraph().getGraphViewWidget()
            self.__inspectorWidget = BackdropInspector(parent=parentWidget,
                                                       nodeItem=self)
            result = self.__inspectorWidget.exec_()
        else:
            self.__inspectorWidget.setFocus()

        super(KBackdrop, self).mouseDoubleClickEvent(event)

    def hoverEnterEvent(self, event):
        self.__hoveredOver = True
        self.update()

    def hoverMoveEvent(self, event):

        resizeCorner = self.getCorner(event.pos())
        resizeEdge = self.getEdge(event.pos())
        if resizeCorner == 0:
            self.__setCustomCursor = True
            self.setCursor(QtCore.Qt.SizeFDiagCursor)
        elif resizeCorner == 1:
            self.__setCustomCursor = True
            self.setCursor(QtCore.Qt.SizeBDiagCursor)
        elif resizeCorner == 2:
            self.__setCustomCursor = True
            self.setCursor(QtCore.Qt.SizeBDiagCursor)
        elif resizeCorner == 3:
            self.__setCustomCursor = True
            self.setCursor(QtCore.Qt.SizeFDiagCursor)
        elif resizeEdge == 0:
            self.__setCustomCursor = True
            self.setCursor(QtCore.Qt.SizeVerCursor)
        elif resizeEdge == 1:
            self.__setCustomCursor = True
            self.setCursor(QtCore.Qt.SizeHorCursor)
        elif resizeEdge == 2:
            self.__setCustomCursor = True
            self.setCursor(QtCore.Qt.SizeVerCursor)
        elif resizeEdge == 3:
            self.__setCustomCursor = True
            self.setCursor(QtCore.Qt.SizeHorCursor)
        elif self.__headerItem.contains(event.pos()) is True:
            self.__setCustomCursor = True
            self.setCursor(QtCore.Qt.OpenHandCursor)
        else:
            if self.__setCustomCursor is True:
                self.setCursor(QtCore.Qt.ArrowCursor)

    def hoverLeaveEvent(self, event):
        self.setCursor(QtCore.Qt.ArrowCursor)
        self.__hoveredOver = False
        self.update()

    # =============
    # Misc Methods
    # =============
    def getCorner(self, pos):
        topLeft = self.mapFromItem(self, self.boundingRect().topLeft())
        bottomRight = self.mapFromItem(self, self.boundingRect().bottomRight())
        rect = QtCore.QRectF(topLeft, bottomRight)

        if (rect.topLeft() - pos).manhattanLength() < self.__resizeDistance:
            return 0
        elif (rect.topRight() - pos).manhattanLength() < self.__resizeDistance:
            return 1
        elif (rect.bottomLeft() -
              pos).manhattanLength() < self.__resizeDistance:
            return 2
        elif (rect.bottomRight() -
              pos).manhattanLength() < self.__resizeDistance:
            return 3

        return -1

    def getEdge(self, pos):
        topRectUpperLeft = self.mapFromItem(self,
                                            self.boundingRect().topLeft())
        topRectLowerRight = self.mapFromItem(self,
                                             self.boundingRect().right(),
                                             self.__resizeDistance * 0.25)
        topRect = QtCore.QRectF(topRectUpperLeft, topRectLowerRight)

        rightRectUpperLeft = self.mapFromItem(
            self,
            self.boundingRect().right() - self.__resizeDistance * 0.25, 0)
        rightRectLowerRight = self.mapFromItem(
            self,
            self.boundingRect().bottomRight())
        rightRect = QtCore.QRectF(rightRectUpperLeft, rightRectLowerRight)

        bottomRectUpperLeft = self.mapFromItem(
            self, 0,
            self.boundingRect().bottom() - self.__resizeDistance * 0.25)
        bottomRectLowerRight = self.mapFromItem(
            self,
            self.boundingRect().bottomRight())
        bottomRect = QtCore.QRectF(bottomRectUpperLeft, bottomRectLowerRight)

        leftRectUpperLeft = self.mapFromItem(self,
                                             self.boundingRect().topLeft())
        leftRectLowerRight = self.mapFromItem(
            self,
            self.boundingRect().left() + self.__resizeDistance * 0.25,
            self.boundingRect().bottom())
        leftRect = QtCore.QRectF(leftRectUpperLeft, leftRectLowerRight)

        if topRect.contains(pos):
            return -1  # Disabling the top resize by default.
        elif rightRect.contains(pos):
            return 1
        elif bottomRect.contains(pos):
            return 2
        elif leftRect.contains(pos):
            return 3

        return -1

    def inspectorClosed(self):
        self.__inspectorWidget = None

    # ==========
    # Shut Down
    # ==========
    def disconnectAllPorts(self):
        pass

    # =============
    # Data Methods
    # =============
    def getData(self):
        """Gets the essential data of the backdrop for saving.

        Returns:
            dict: Data for saving.

        """

        data = {
            'name': self.getName(),
            'comment': self.getComment(),
            'graphPos': self.getGraphPos().toTuple(),
            'size': self.size().toTuple(),
            'color': self.getColor().toTuple()
        }

        return data

    def setData(self, data):
        """Sets the data on a backdrop after loading.

        Args:
            data (dict): Name, comment, graph pos, size, and color.

        Returns:
            bool: True if successful.

        """

        self.setComment(data.get('comment', ''))

        size = data.get('size', (self.minimumWidth(), self.minimumHeight()))
        self.resize(size[0], size[1])

        position = data.get('graphPos', (0, 0))
        self.setGraphPos(QtCore.QPointF(position[0], position[1]))

        color = data.get('color', self.__defaultColor.toTuple())
        self.setColor(
            color=QtGui.QColor(color[0], color[1], color[2], color[3]))
        self.setUnselectedColor(self.getColor().darker(125))
        self.setSelectedColor(self.getColor().lighter(175))
        self.setHoveredColor(self.getColor().lighter(110))

        return True