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)
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()
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
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
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()
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)
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)
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())
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