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, 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))
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()
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): super (Node, self).__init__ () 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 ("xxx", 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 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 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)
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 __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
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) ################################################ ## 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() # ============= # Copy / Paste # ============= def getClipboardData(self): return self.__class__._clipboardData
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 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)