def __init__(self, graph, srcPortCircle, dstPortCircle): super(Connection, self).__init__() self.__graph = graph self.__srcPortCircle = srcPortCircle self.__dstPortCircle = dstPortCircle penStyle = QtCore.Qt.DashLine self.__connectionColor = QtGui.QColor(0, 0, 0) self.__connectionColor.setRgbF( *self.__srcPortCircle.getColor().getRgbF()) self.__connectionColor.setAlpha(125) self.__defaultPen = QtGui.QPen(self.__connectionColor, 1.5, s=penStyle) self.__defaultPen.setDashPattern([1, 2, 2, 1]) self.__connectionHoverColor = QtGui.QColor(0, 0, 0) self.__connectionHoverColor.setRgbF( *self.__srcPortCircle.getColor().getRgbF()) self.__connectionHoverColor.setAlpha(255) self.__hoverPen = QtGui.QPen(self.__connectionHoverColor, 1.5, s=penStyle) self.__hoverPen.setDashPattern([1, 2, 2, 1]) self.setPen(self.__defaultPen) self.setZValue(-1) self.setAcceptHoverEvents(True) self.connect()
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)
def generateNodes(count, offset, depth): for i in range(count): node1 = Node(graph, 'node' + str(depth) + str(i)) node1.addPort( InputPort(node1, graph, 'InPort', QtGui.QColor(128, 170, 170, 255), 'MyDataX')) node1.addPort( OutputPort(node1, graph, 'OutPort', QtGui.QColor(32, 255, 32, 255), 'MyDataX')) node1.setGraphPos(QtCore.QPointF(offset, i * 80)) graph.addNode(node1) global totalCount totalCount += 1 if depth < 6: generateNodes(count * 2, offset + 160, depth + 1) for i in range(count): graph.connectPorts('node' + str(depth) + str(i), 'OutPort', 'node' + str(depth + 1) + str(i * 2), 'InPort') graph.connectPorts('node' + str(depth) + str(i), 'OutPort', 'node' + str(depth + 1) + str(i * 2 + 1), 'InPort') elif depth < 12: generateNodes(int(count / 2), offset + 160, depth + 1) for i in range(count / 2): graph.connectPorts('node' + str(depth) + str(i), 'OutPort', 'node' + str(depth + 1) + str(int(i)), 'InPort')
def __init__(self, port, graph, hOffset, color, connectionPointType): super(KNodePortCircle, self).__init__(port, graph, hOffset, color, connectionPointType) if self.getPort().getDataType().endswith('[]'): self.setDefaultPen(QtGui.QPen(QtGui.QColor(204, 0, 0), 1.5)) self.setHoverPen(QtGui.QPen(QtGui.QColor(255, 155, 100), 2.0))
class SelectionRect(QtWidgets.QGraphicsWidget): __backgroundColor = QtGui.QColor(100, 100, 100, 50) __pen = QtGui.QPen(QtGui.QColor(25, 25, 25), 1.0, QtCore.Qt.DashLine) def __init__(self, graph, mouseDownPos): super(SelectionRect, self).__init__() self.setZValue(-1) self.__graph = graph self.__graph.scene().addItem(self) self.__mouseDownPos = mouseDownPos self.setPos(self.__mouseDownPos) self.resize(0, 0) def setDragPoint(self, dragPoint): topLeft = QtCore.QPointF(self.__mouseDownPos) bottomRight = QtCore.QPointF(dragPoint) if dragPoint.x() < self.__mouseDownPos.x(): topLeft.setX(dragPoint.x()) bottomRight.setX(self.__mouseDownPos.x()) if dragPoint.y() < self.__mouseDownPos.y(): topLeft.setY(dragPoint.y()) bottomRight.setY(self.__mouseDownPos.y()) self.setPos(topLeft) self.resize(bottomRight.x() - topLeft.x(), bottomRight.y() - topLeft.y()) def paint(self, painter, option, widget): rect = self.windowFrameRect() painter.setBrush(self.__backgroundColor) painter.setPen(self.__pen) painter.drawRect(rect) def destroy(self): self.__graph.scene().removeItem(self)
def getPortColor(dataType): if dataType.startswith('Xfo'): return QtGui.QColor(128, 170, 170, 255) elif dataType.startswith('Float'): return QtGui.QColor(32, 255, 32, 255) elif dataType.startswith('Integer'): return QtGui.QColor(0, 128, 0, 255) elif dataType.startswith('Boolean'): return QtGui.QColor(255, 102, 0, 255) else: return QtGui.QColor(50, 205, 254, 255)
class NodeTitle(QtWidgets.QGraphicsWidget): __color = QtGui.QColor(25, 25, 25) __font = QtGui.QFont('Decorative', 14) __font.setLetterSpacing(QtGui.QFont.PercentageSpacing, 115) __labelBottomSpacing = 12 def __init__(self, text, parent=None): super(NodeTitle, self).__init__(parent) self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)) self.__textItem = QtWidgets.QGraphicsTextItem(text, self) self.__textItem.setDefaultTextColor(self.__color) self.__textItem.setFont(self.__font) self.__textItem.setPos(0, -2) option = self.__textItem.document().defaultTextOption() option.setWrapMode(QtGui.QTextOption.NoWrap) self.__textItem.document().setDefaultTextOption(option) self.__textItem.adjustSize() self.setPreferredSize(self.textSize()) def setText(self, text): self.__textItem.setPlainText(text) self.__textItem.adjustSize() self.setPreferredSize(self.textSize()) def textSize(self): return QtCore.QSizeF( self.__textItem.textWidth(), self.__font.pointSizeF() + self.__labelBottomSpacing )
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
def dragObject(self): if not self.selectedIndexes(): return item = self.selectedItems()[0] role = item.data(0, QtCore.Qt.UserRole) if role == 'Folder': return text = 'KrakenComponent:' + role mimeData = QtCore.QMimeData() mimeData.setText(text) drag = QtGui.QDrag(self) drag.setMimeData(mimeData) drag.setHotSpot(QtCore.QPoint(90, 23)) ghostComponent = QtGui.QPixmap(180, 46) ghostComponent.fill(QtGui.QColor(67, 143, 153, 80)) drag.setPixmap(ghostComponent) drag.start(QtCore.Qt.IgnoreAction)
def write(self, msg, level): if level == 'DEBUG': messageColor = QtGui.QColor("#EEE97B") elif level == 'INFO': messageColor = QtGui.QColor(QtCore.Qt.white) elif level == 'INFORM': messageColor = QtGui.QColor("#009900") elif level == 'WARNING': messageColor = QtGui.QColor("#D89614") elif level == 'ERROR': messageColor = QtGui.QColor("#CC0000") elif level == 'CRITICAL': messageColor = QtGui.QColor("#CC0000") else: messageColor = QtGui.QColor(QtCore.Qt.white) self.textWidget.setTextColor(messageColor) charFormat = self.textWidget.currentCharFormat() textCursor = self.textWidget.textCursor() textCursor.movePosition(QtGui.QTextCursor.End) textCursor.insertText('[ ' + level + ' ] ' + msg, charFormat) self.textWidget.setTextCursor(textCursor) self.textWidget.ensureCursorVisible()
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)
class KBackdropTitle(QtWidgets.QGraphicsWidget): __color = QtGui.QColor(255, 255, 255) __font = QtGui.QFont('Helvetica', 11) __font.setLetterSpacing(QtGui.QFont.PercentageSpacing, 120) __fontMetrics = QtGui.QFontMetrics(__font) __labelBottomSpacing = 4 def __init__(self, text, parent=None): super(KBackdropTitle, self).__init__(parent) self.parentWidget = parent self.__textItem = QtWidgets.QGraphicsTextItem(text, self) self.__textItem.setDefaultTextColor(self.__color) self.__textItem.setFont(self.__font) self.__textItem.setPos(0, 1) option = self.__textItem.document().defaultTextOption() self.__textItem.document().setDefaultTextOption(option) self.__textItem.adjustSize() self.__textItem.setTextWidth(120) self.setPreferredSize(self.textSize()) def setText(self, text): self.__textItem.setPlainText(text) # self.__textItem.adjustSize() self.nodeResized(self.parentWidget.parentWidget.size().width()) def setTextColor(self, color): self.__color = color self.update() def textSize(self): return QtCore.QSizeF(self.__textItem.textWidth(), self.textHeight()) def textHeight(self): return self.__textItem.document().documentLayout().documentSize( ).height() + self.__labelBottomSpacing def nodeResized(self, width=None): fmWidth = self.__fontMetrics.width(self.__textItem.toPlainText()) newWidth = min(fmWidth, width) if width > fmWidth: newWidth = width self.__textItem.setTextWidth(newWidth) self.setPreferredSize(newWidth, self.textHeight()) def getBackdropWidget(self): return self.parent().parent()
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 __init__(self, port, graph, hOffset, color, connectionPointType): super(PortCircle, self).__init__(port) self.__port = port self._graph = graph self._connectionPointType = connectionPointType self.__connections = set() self._supportsOnlySingleConnections = connectionPointType == 'In' self.setSizePolicy( QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)) size = QtCore.QSizeF(self.__diameter, self.__diameter) self.setPreferredSize(size) self.setWindowFrameMargins(0, 0, 0, 0) self.transform().translate(self.__radius * hOffset, 0) self.__defaultPen = QtGui.QPen(QtGui.QColor(25, 25, 25), 1.0) self.__hoverPen = QtGui.QPen(QtGui.QColor(255, 255, 100), 1.5) self._ellipseItem = QtWidgets.QGraphicsEllipseItem(self) self._ellipseItem.setPen(self.__defaultPen) self._ellipseItem.setPos(size.width() / 2, size.height() / 2) self._ellipseItem.setRect( -self.__radius, -self.__radius, self.__diameter, self.__diameter, ) if connectionPointType == 'In': self._ellipseItem.setStartAngle(270 * 16) self._ellipseItem.setSpanAngle(180 * 16) self.setColor(color) self.setAcceptHoverEvents(True)
def __init__(self, graph, component): super(KNode, self).__init__(graph, component.getDecoratedName()) self.__component = component self.__inspectorWidget = None for i in range(self.__component.getNumInputs()): componentInput = component.getInputByIndex(i) self.addPort(KNodeInputPort(self, graph, componentInput)) for i in range(self.__component.getNumOutputs()): componentOutput = component.getOutputByIndex(i) self.addPort(KNodeOutputPort(self, graph, componentOutput)) self.setGraphPos( QtCore.QPointF(self.__component.getGraphPos().x, self.__component.getGraphPos().y)) nodeColor = component.getComponentColor() self.setColor( QtGui.QColor(nodeColor[0], nodeColor[1], nodeColor[2], nodeColor[3])) self.setUnselectedColor(self.getColor().darker(125)) self.setSelectedColor(self.getColor().lighter(175))
def __onColorChanged(self, qcolor): self.__color = QtGui.QColor(qcolor.redF() * 255, qcolor.greenF() * 255, qcolor.blueF() * 255) self._qgraphcsView.setBackgroundBrush(self.__color) self._setValueToController()
def getGraphView(self): return self.graphView if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) widget = GraphViewWidget() graph = GraphView(parent=widget) from node import Node from port import InputPort, OutputPort, IOPort node1 = Node(graph, 'Short') node1.addPort( InputPort(node1, graph, 'InPort1', QtGui.QColor(128, 170, 170, 255), 'MyDataX')) node1.addPort( InputPort(node1, graph, 'InPort2', QtGui.QColor(128, 170, 170, 255), 'MyDataX')) node1.addPort( OutputPort(node1, graph, 'OutPort', QtGui.QColor(32, 255, 32, 255), 'MyDataY')) node1.addPort( IOPort(node1, graph, 'IOPort1', QtGui.QColor(32, 255, 32, 255), 'MyDataY')) node1.addPort( IOPort(node1, graph, 'IOPort2', QtGui.QColor(32, 255, 32, 255), 'MyDataY')) node1.setGraphPos(QtCore.QPointF(-100, 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
def setNodeColor(self, color): self.nodeItem.setColor(QtGui.QColor(color)) self.nodeItem.setUnselectedColor(self.nodeItem.getColor().darker(125)) self.nodeItem.setSelectedColor(self.nodeItem.getColor().lighter(175)) self.nodeItem.setHoveredColor(self.nodeItem.getColor().lighter(110))
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)
def setEditorValue(self, value): self.__color = QtGui.QColor(value.r * 255, value.g * 255, value.b * 255) self._qgraphcsView.setBackgroundBrush(self.__color) self._qgraphcsView.update()
def getColor(self): color = QtGui.QColor(self.__color) color.setAlpha(255) return color
class BasePort(QtWidgets.QGraphicsWidget): _labelColor = QtGui.QColor(25, 25, 25) _labelHighlightColor = QtGui.QColor(225, 225, 225, 255) def __init__(self, parent, graph, name, color, dataType, connectionPointType): super(BasePort, self).__init__(parent) self._node = parent self._graph = graph self._name = name self._dataType = dataType self._connectionPointType = connectionPointType self.setSizePolicy( QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)) layout = QtWidgets.QGraphicsLinearLayout() layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) self._color = color self._inCircle = None self._outCircle = None self._labelItem = None self._inCircleHolder = ItemHolder(self) self._outCircleHolder = ItemHolder(self) self._labelItemHolder = ItemHolder(self) self.layout().addItem(self._inCircleHolder) self.layout().setAlignment( self._inCircleHolder, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter) self.layout().addItem(self._labelItemHolder) self.layout().setAlignment( self._labelItemHolder, QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) self.layout().addItem(self._outCircleHolder) self.layout().setAlignment( self._outCircleHolder, QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) def getName(self): return self._name def getDataType(self): return self._dataType def getNode(self): return self._node def getGraph(self): return self._graph def getColor(self): return self._color def setColor(self, color): if self._inCircle is not None: self._inCircle.setColor(color) if self._outCircle is not None: self._outCircle.setColor(color) self._color = color def inCircle(self): return self._inCircle def setInCircle(self, inCircle): self._inCircleHolder.setItem(inCircle) self._inCircle = inCircle self.layout().insertStretch(2, 2) self.updatecontentMargins() def outCircle(self): return self._outCircle def setOutCircle(self, outCircle): self._outCircleHolder.setItem(outCircle) self._outCircle = outCircle self.layout().insertStretch(1, 2) self.updatecontentMargins() def updatecontentMargins(self): left = 0 right = 0 if self._inCircle is None: left = 30 if self._outCircle is None: right = 30 self.layout().setContentsMargins(left, 0, right, 0) def labelItem(self): return self._labelItem def setLabelItem(self, labelItem): self._labelItemHolder.setItem(labelItem) self._labelItem = labelItem # =================== # Connection Methods # =================== def connectionPointType(self): return self._connectionPointType
class Connection(QtWidgets.QGraphicsPathItem): __defaultPen = QtGui.QPen(QtGui.QColor(168, 134, 3), 1.5) def __init__(self, graph, srcPortCircle, dstPortCircle): super(Connection, self).__init__() self.__graph = graph self.__srcPortCircle = srcPortCircle self.__dstPortCircle = dstPortCircle penStyle = QtCore.Qt.DashLine self.__connectionColor = QtGui.QColor(0, 0, 0) self.__connectionColor.setRgbF( *self.__srcPortCircle.getColor().getRgbF()) self.__connectionColor.setAlpha(125) self.__defaultPen = QtGui.QPen(self.__connectionColor, 1.5, s=penStyle) self.__defaultPen.setDashPattern([1, 2, 2, 1]) self.__connectionHoverColor = QtGui.QColor(0, 0, 0) self.__connectionHoverColor.setRgbF( *self.__srcPortCircle.getColor().getRgbF()) self.__connectionHoverColor.setAlpha(255) self.__hoverPen = QtGui.QPen(self.__connectionHoverColor, 1.5, s=penStyle) self.__hoverPen.setDashPattern([1, 2, 2, 1]) self.setPen(self.__defaultPen) self.setZValue(-1) self.setAcceptHoverEvents(True) self.connect() def setPenStyle(self, penStyle): self.__defaultPen.setStyle(penStyle) self.__hoverPen.setStyle(penStyle) self.setPen(self.__defaultPen) # Force a redraw def setPenWidth(self, width): self.__defaultPen.setWidthF(width) self.__hoverPen.setWidthF(width) self.setPen(self.__defaultPen) # Force a redraw def getSrcPortCircle(self): return self.__srcPortCircle def getDstPortCircle(self): return self.__dstPortCircle def getSrcPort(self): return self.__srcPortCircle.getPort() def getDstPort(self): return self.__dstPortCircle.getPort() def boundingRect(self): srcPoint = self.mapFromScene( self.__srcPortCircle.centerInSceneCoords()) dstPoint = self.mapFromScene( self.__dstPortCircle.centerInSceneCoords()) penWidth = self.__defaultPen.width() return QtCore.QRectF( min(srcPoint.x(), dstPoint.x()), min(srcPoint.y(), dstPoint.y()), abs(dstPoint.x() - srcPoint.x()), abs(dstPoint.y() - srcPoint.y()), ).adjusted(-penWidth / 2, -penWidth / 2, +penWidth / 2, +penWidth / 2) def paint(self, painter, option, widget): srcPoint = self.mapFromScene( self.__srcPortCircle.centerInSceneCoords()) dstPoint = self.mapFromScene( self.__dstPortCircle.centerInSceneCoords()) dist_between = dstPoint - srcPoint self.__path = QtGui.QPainterPath() self.__path.moveTo(srcPoint) self.__path.cubicTo( srcPoint + QtCore.QPointF(dist_between.x() * 0.4, 0), dstPoint - QtCore.QPointF(dist_between.x() * 0.4, 0), dstPoint) self.setPath(self.__path) super(Connection, self).paint(painter, option, widget) def hoverEnterEvent(self, event): self.setPen(self.__hoverPen) super(Connection, self).hoverEnterEvent(event) def hoverLeaveEvent(self, event): self.setPen(self.__defaultPen) super(Connection, self).hoverLeaveEvent(event) def mousePressEvent(self, event): if event.button() is QtCore.Qt.MouseButton.LeftButton: self.__dragging = True self._lastDragPoint = self.mapToScene(event.pos()) event.accept() else: super(Connection, self).mousePressEvent(event) def mouseMoveEvent(self, event): if self.__dragging: pos = self.mapToScene(event.pos()) delta = pos - self._lastDragPoint if delta.x() != 0: self.__graph.removeConnection(self) import mouse_grabber if delta.x() < 0: mouse_grabber.MouseGrabber(self.__graph, pos, self.__srcPortCircle, 'In') else: mouse_grabber.MouseGrabber(self.__graph, pos, self.__dstPortCircle, 'Out') else: super(Connection, self).mouseMoveEvent(event) def disconnect(self): self.__srcPortCircle.removeConnection(self) self.__dstPortCircle.removeConnection(self) def connect(self): self.__srcPortCircle.addConnection(self) self.__dstPortCircle.addConnection(self)
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)