def mousePressEvent(self, event): if event.button() == QtCore.Qt.LeftButton and self.itemAt( event.pos()) is None: self.begin_node_selection.emit() self._manipulation_mode = 1 self._mouse_down_selection = copy.copy(self.get_selected_nodes()) self.clear_selection(emit_signal=False) self._selection_rect = SelectionRect( graph=self, mouse_down_pos=self.mapToScene(event.pos())) elif event.button() is QtCore.Qt.MidButton: self.setCursor(QtCore.Qt.OpenHandCursor) self._manipulation_mode = 2 self._last_pan_point = self.mapToScene(event.pos()) else: super(GraphView, self).mousePressEvent(event)
def create_selection(self, selection_type, event=None, device_id=None): """ Add a selection type to the controller. :param selection_type: Type of the selection. :type selection_type: SelectionType :param event: Initial touch event, used to track subsequent touches for lasso selection. :type event: CursorEvent :param device_id: The id of the device. :type device_id: int :return: The newly created selection. :rtype: Selection """ if event is not None: index = "t_" + str(event.contact.id) else: index = "d_" + str(device_id) if index not in self.__selections: if selection_type == SelectionType.RECT_SELECTION: self.__selections[index] = SelectionRect(self.__div) elif selection_type == SelectionType.LASSO_SELECTION: self.__selections[index] = SelectionLasso(self.__div, event) return self.__selections[index]
def mousePressEvent(self, event): if event.button() is QtCore.Qt.MouseButton.LeftButton and self.itemAt( event.pos()) is None: self.beginNodeSelection.emit() self._manipulationMode = 1 self._mouseDownSelection = copy.copy(self.getSelectedNodes()) self.clearSelection(emitSignal=False) 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 = 2 self._lastPanPoint = self.mapToScene(event.pos()) else: super(GraphView, self).mousePressEvent(event)
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 mousePressEvent(self, event): if event.button() == QtCore.Qt.LeftButton and self.itemAt(event.pos()) is None: self.begin_node_selection.emit() self._manipulation_mode = 1 self._mouse_down_selection = copy.copy(self.get_selected_nodes()) self.clear_selection(emit_signal=False) self._selection_rect = SelectionRect(graph=self, mouse_down_pos=self.mapToScene(event.pos())) elif event.button() is QtCore.Qt.MidButton: self.setCursor(QtCore.Qt.OpenHandCursor) self._manipulation_mode = 2 self._last_pan_point = self.mapToScene(event.pos()) else: super(GraphView, self).mousePressEvent(event)
def mousePressEvent(self, event): if event.button() is QtCore.Qt.MouseButton.LeftButton and self.itemAt(event.pos()) is None: self.beginNodeSelection.emit() self._manipulationMode = 1 self._mouseDownSelection = copy.copy(self.getSelectedNodes()) self.clearSelection(emitSignal=False) 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 = 2 self._lastPanPoint = self.mapToScene(event.pos()) else: super(GraphView, self).mousePressEvent(event)
class GraphView(QtGui.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(QtGui.QGraphicsScene()) self.__connections = set() self.__nodes = {} self.__selection = set() self._manipulationMode = 0 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.getPort(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.getPort(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 = 1 self._mouseDownSelection = copy.copy(self.getSelectedNodes()) self.clearSelection(emitSignal=False) 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 = 2 self._lastPanPoint = self.mapToScene(event.pos()) else: super(GraphView, self).mousePressEvent(event) def mouseMoveEvent(self, event): if self._manipulationMode == 1: dragPoint = self.mapToScene(event.pos()) self._selectionRect.setDragPoint(dragPoint) for name, node in self.__nodes.iteritems(): if not node.isSelected( ) and self._selectionRect.collidesWithItem(node): self.selectNode(node, emitSignal=False) elif self._manipulationMode == 2: 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 == 3: 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()) else: super(GraphView, self).mouseMoveEvent(event) def mouseReleaseEvent(self, event): if self._manipulationMode == 1: self._selectionRect.destroy() self._selectionRect = None self._manipulationMode = 0 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 == 2: self.setCursor(QtCore.Qt.ArrowCursor) self._manipulationMode = 0 else: super(GraphView, self).mouseReleaseEvent(event) def wheelEvent(self, event): (xfo, invRes) = self.transform().inverted() topLeft = xfo.map(self.rect().topLeft()) bottomRight = xfo.map(self.rect().bottomRight()) center = (topLeft + bottomRight) * 0.5 zoomFactor = 1.0 + event.delta() * self._mouseWheelZoomRate transform = self.transform() # Limit zoom to 3x if transform.m22() * zoomFactor >= 2.0: return self.scale(zoomFactor, zoomFactor) # 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 GraphView(QtGui.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(QtGui.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 = QtGui.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 presss sceneCenter = self.sceneRect().center() scenePoint = self.mapToScene(event.pos()) posFromSceneCenter = scenePoint - sceneCenter rect = self.sceneRect() rect.translate(-1 * 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 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 GraphView(QtGui.QGraphicsView): """ View for the node graph scene """ node_added = QtCore.pyqtSignal(Node) node_removed = QtCore.pyqtSignal(Node) node_name_changed = QtCore.pyqtSignal(str, str) selection_changed = QtCore.pyqtSignal(list, list) selection_moved = QtCore.pyqtSignal(set, QtCore.QPointF) end_selection_moved = QtCore.pyqtSignal(set, QtCore.QPointF) begin_node_selection = QtCore.pyqtSignal() end_node_selection = QtCore.pyqtSignal() connection_added = QtCore.pyqtSignal(Connection) connection_removed = QtCore.pyqtSignal(Connection) begin_connection_manipulation = QtCore.pyqtSignal() end_connection_manipulation = QtCore.pyqtSignal() _size = QtCore.QSize(900, 600) _grid_size = 30 _grid_size_course = 300 _background_color = QtGui.QColor(50, 50, 50) _grid_penS = QtGui.QPen(QtGui.QColor(44, 44, 44, 255), 0.5) _grid_penL = QtGui.QPen(QtGui.QColor(40, 40, 40, 255), 1.0) _mouse_wheel_zoom_rate = 0.0005 _snap_to_grid = False def __init__(self, parent=None): super(GraphView, self).__init__(parent) self._parent = parent self.setRenderHint(QtGui.QPainter.Antialiasing) self.setRenderHint(QtGui.QPainter.TextAntialiasing) self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.resize(self._size) self.setSceneRect(-self._size.width() * 0.5, -self._size.height() * 0.5, self._size.width(), self._size.height()) self.setAcceptDrops(True) self.refresh() @property def grid_size(self): """ Return grid size :return: int """ return self._grid_size @property def snap_to_grid(self): """ get snap to grid stat :return: bool """ return self._snap_to_grid def refresh(self): """ Refresh Gui with setting all necessary items :return: None """ # Set Graphics scene self.setScene(QtGui.QGraphicsScene()) self._connections = set() self._nodes = {} self._selection = set() self._manipulation_mode = 0 self._selection_rect = None def add_node(self, node, emit_signal=True): """ :param node to add: :param emit_signal: emit node_add signal :return: """ self.scene().addItem(node) self._nodes[node.name] = node node.name_changed.connect(self._on_node_name_changed) if emit_signal: self.node_added.emit(node) return node def select_node(self, node, clear_selection=False, emit_signal=True): """ To highlight the given node :param node: node to highlight :return: None """ prev_selection = [] if emit_signal: for n in self._selection: prev_selection.append(n) if clear_selection is True: self.clear_selection(emit_signal=False) if node in self._selection: raise IndexError("Node is already in selection!") node.set_selected(True) self._selection.add(node) if emit_signal: new_selection = [] for n in self._selection: new_selection.append(n) self.selection_changed.emit(prev_selection, new_selection) def deselect_node(self, node, emit_signal=True): """ To deselect the given node :param node: node given :return: None """ if node not in self._selection: raise IndexError("Node is not in selection!") prev_selection = [] if emit_signal: for n in self._selection: prev_selection.append(n) node.set_selected(False) self._selection.remove(node) if emit_signal: new_selection = [] for n in self._selection: new_selection.append(n) self.selection_changed.emit(prev_selection, new_selection) def clear_selection(self, emit_signal=True): """ Clear nodes selection :param emitSignal: emit signal :return: None """ prev_selection = [] if emit_signal: for node in self._selection: prev_selection.append(node) for node in self._selection: node.set_selected(False) self._selection.clear() if emit_signal and len(prev_selection) != 0: self.selection_changed.emit(prev_selection, []) def get_selected_nodes(self): """ Get all selected node :return: None """ return self._selection def delete_selected_nodes(self): """ Delete selected nodes :return: None """ self.begin_delete_selection.emit() selected_nodes = self.get_selected_nodes() names = "" for node in selected_nodes: node.disconnect_all_ports() self.remove_node(node) self.end_delete_selection.emit() def move_selected_nodes(self, delta, emit_signal=True): """ Move all selected nodes to delta provided :param delta: delta value :param emit_signal: emit signal stat :return: None """ for node in self._selection: node.translate(delta.x(), delta.y()) if emit_signal: self.selection_moved.emit(self._selection, delta) def end_move_selected_nodes(self, delta): """ Report end of moving selected nodes :param delta: :return: None """ self.end_selection_moved.emit(self._selection, delta) def _on_node_name_changed(self, oldname, newname): """ Slot connected on node name changed :param oldname: old node name :param newname: new node name :return: None """ if newname in self._nodes and self._nodes[oldname] != self._nodes[newname]: raise Exception("New name collides with existing node.") node = self._nodes[oldname] self._nodes[newname] = node del self.__nodes[oldname] self.node_name_changed.emit(oldname, newname) def drawBackground(self, painter, rect): """ override to drawBackground method to draw custom background :param painter: painter object :param rect: view rectangle :return: None """ painter.fillRect(rect, self._background_color) left = int(rect.left()) - (int(rect.left()) % self._grid_size) top = int(rect.top()) - (int(rect.top()) % self._grid_size) # Draw horizontal fine lines grid_lines = [] painter.setPen(self._grid_penS) y = float(top) while y < float(rect.bottom()): grid_lines.append(QtCore.QLineF(rect.left(), y, rect.right(), y)) y += self._grid_size painter.drawLines(grid_lines) # Draw vertical lines grid_lines = [] painter.setPen(self._grid_penL) x = float(left) while x < float(rect.right()): grid_lines.append(QtCore.QLineF(x, rect.top(), x, rect.bottom())) x += self._grid_size painter.drawLines(grid_lines) # Draw thick grid left = int(rect.left()) - (int(rect.left()) % self._grid_size_course) top = int(rect.top()) - (int(rect.top()) % self._grid_size_course) # Draw vertical thick lines grid_lines = [] painter.setPen(self._grid_penL) x = left while x < rect.right(): grid_lines.append(QtCore.QLineF(x, rect.top(), x, rect.bottom())) x += self._grid_size_course painter.drawLines(grid_lines) # Draw horizontal thick lines grid_lines = [] painter.setPen(self._grid_penL) y = top while y < rect.bottom(): grid_lines.append(QtCore.QLineF(rect.left(), y, rect.right(), y)) y += self._grid_size_course painter.drawLines(grid_lines) return super(GraphView, self).drawBackground(painter, rect) # ------------------------ # Connections # ------------------------ def emit_begin_connection_manipulation_signal(self): self.begin_connection_manipulation.emit() def emit_end_connection_manipulation_signal(self): self.end_connection_manipulation.emit() def add_connection(self, connection, emit_signal=True): """ to add new connection :param connection: new connection :param emit_signal: emit signal :return: connection """ self._connections.add(connection) self.scene().addItem(connection) if emit_signal: self.connection_added.emit(connection) return connection def remove_connection(self, connection, emit_signal=True): """ Remove existing connection :param connection: connection to remove :param emit_signal: emit signal ? :return:None """ connection.disconnect() self._connections.remove(connection) self.scene().removeItem(connection) if emit_signal: self.connection_removed.emit(connection) def get_node(self, name): """ Get node from node list :param name: name of the node :return: Node else None """ if name in self._nodes: return self._nodes[name] return None def connect_ports(self, src_node, output_name, tgt_node, input_name): """ Connect between ports :param src_node: source node :param output_name: outut name :param tgt_node: target node :param input_name: input name :return: Connection object """ if isinstance(src_node, Node): source_node = src_node elif isinstance(src_node, basestring): source_node = self.get_node(src_node) if not source_node: raise Exception("Node not found:" + str(src_node)) else: raise Exception("Invalid src_node:" + str(src_node)) source_port = source_node.get_port(output_name) if not source_port: raise Exception("Node '" + source_node.getName() + "' does not have output:" + output_name) if isinstance(tgt_node, Node): target_node = tgt_node elif isinstance(tgt_node, basestring): target_node = self.get_node(tgt_node) if not target_node: raise Exception("Node not found:" + str(tgt_node)) else: raise Exception("Invalid tgt_node:" + str(tgt_node)) target_port = target_node.getPort(input_name) if not target_port: raise Exception("Node '" + target_node.name() + "' does not have input:" + input_name) connection = Connection(self, source_port.out_circle, target_port.in_circle) self.add_connection(connection, emitSignal=False) return connection # -------------------------- # Events # -------------------------- def mousePressEvent(self, event): if event.button() == QtCore.Qt.LeftButton and self.itemAt(event.pos()) is None: self.begin_node_selection.emit() self._manipulation_mode = 1 self._mouse_down_selection = copy.copy(self.get_selected_nodes()) self.clear_selection(emit_signal=False) self._selection_rect = SelectionRect(graph=self, mouse_down_pos=self.mapToScene(event.pos())) elif event.button() is QtCore.Qt.MidButton: self.setCursor(QtCore.Qt.OpenHandCursor) self._manipulation_mode = 2 self._last_pan_point = self.mapToScene(event.pos()) else: super(GraphView, self).mousePressEvent(event) def mouseMoveEvent(self, event): if self._manipulation_mode == 1: drag_point = self.mapToScene(event.pos()) self._selection_rect.set_drag_point(drag_point) for name, node in self._nodes.iteritems(): if not node.is_selected() and self._selection_rect.collidesWithItem(node): self.select_node(node, emit_signal=False) elif self._manipulation_mode == 2: delta = self.mapToScene(event.pos()) - self._last_pan_point rect = self.sceneRect() rect.translate(-delta.x(), -delta.y()) self.setSceneRect(rect) self._last_pan_point = self.mapToScene(event.pos()) elif self._manipulation_mode == 3: new_pos = self.mapToScene(event.pos()) delta = new_pos - self._last_drag_point self._last_drag_point = new_pos selected_nodes = self.get_selected_nodes() # Apply the delta to each selected node for node in selected_nodes: node.translate(delta.x(), delta.y()) else: super(GraphView, self).mouseMoveEvent(event) def mouseReleaseEvent(self, event): if self._manipulation_mode == 1: self._selection_rect.destroy() self._selection_rect = None self._manipulation_mode = 0 selection = self.get_selected_nodes() deselected_nodes = [] selected_nodes = [] for node in self._mouse_down_selection: if node not in selection: deselected_nodes.append(node) for node in selection: if node not in self._mouse_down_selection: selected_nodes.append(node) if selected_nodes != deselected_nodes: self.selection_changed.emit(deselected_nodes, selected_nodes) self.end_node_selection.emit() elif self._manipulation_mode == 2: self.setCursor(QtCore.Qt.ArrowCursor) self._manipulation_mode = 0 else: super(GraphView, self).mouseReleaseEvent(event) def wheelEvent(self, event): (xfo, inv_res) = self.transform().inverted() top_left = xfo.map(self.rect().topLeft()) bottom_right = xfo.map(self.rect().bottomRight()) center = (top_left + bottom_right) * 0.5 zoom_factor = 1.0 + event.delta() * self._mouse_wheel_zoom_rate transform = self.transform() # Limit zoom to 3x if transform.m22() * zoom_factor >= 2.0: return self.scale(zoom_factor, zoom_factor) # Call udpate to redraw background self.update()
class GraphView(QtGui.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 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(QtGui.QGraphicsScene()) self.__connections = set() self.__nodes = {} self.__selection = set() self._manipulationMode = 0 self._selectionRect = None ################################################ ## 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 _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): sourceNode = self.getNode(srcNode) if not sourceNode: raise Exception("Node not found:" + sourceNode.getName()) sourcePort = sourceNode.getPort(outputName) if not sourcePort: raise Exception("Node '" + sourceNode.getName() + "' does not have output:" + sourcePort.getName()) targetNode = self.getNode(tgtNode) if not targetNode: raise Exception("Node not found:" + targetNode.getName()) targetPort = targetNode.getPort(inputName) if not targetPort: raise Exception("Node '" + targetNode.getName() + "' does not have input:" + targetPort.getName()) 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 = 1 self._mouseDownSelection = copy.copy(self.getSelectedNodes()) self.clearSelection(emitSignal=False) 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 = 2 self._lastPanPoint = self.mapToScene(event.pos()) else: super(GraphView, self).mousePressEvent(event) def mouseMoveEvent(self, event): if self._manipulationMode == 1: dragPoint = self.mapToScene(event.pos()) self._selectionRect.setDragPoint(dragPoint) for name, node in self.__nodes.iteritems(): if not node.isSelected() and self._selectionRect.collidesWithItem(node): self.selectNode(node, emitSignal=False) elif self._manipulationMode == 2: 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 == 3: 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()) else: super(GraphView, self).mouseMoveEvent(event) def mouseReleaseEvent(self, event): if self._manipulationMode == 1: self._selectionRect.destroy() self._selectionRect = None self._manipulationMode = 0 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 == 2: self.setCursor(QtCore.Qt.ArrowCursor) self._manipulationMode = 0 else: super(GraphView, self).mouseReleaseEvent(event) def wheelEvent(self, event): (xfo, invRes) = self.transform().inverted() topLeft = xfo.map(self.rect().topLeft()) bottomRight = xfo.map(self.rect().bottomRight()) center = ( topLeft + bottomRight ) * 0.5 zoomFactor = 1.0 + event.delta() * self._mouseWheelZoomRate transform = self.transform() # Limit zoom to 3x if transform.m22() * zoomFactor >= 2.0: return self.scale(zoomFactor, zoomFactor) # 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 GraphView(QtGui.QGraphicsView): """ View for the node graph scene """ node_added = QtCore.pyqtSignal(Node) node_removed = QtCore.pyqtSignal(Node) node_name_changed = QtCore.pyqtSignal(str, str) selection_changed = QtCore.pyqtSignal(list, list) selection_moved = QtCore.pyqtSignal(set, QtCore.QPointF) end_selection_moved = QtCore.pyqtSignal(set, QtCore.QPointF) begin_node_selection = QtCore.pyqtSignal() end_node_selection = QtCore.pyqtSignal() connection_added = QtCore.pyqtSignal(Connection) connection_removed = QtCore.pyqtSignal(Connection) begin_connection_manipulation = QtCore.pyqtSignal() end_connection_manipulation = QtCore.pyqtSignal() _size = QtCore.QSize(900, 600) _grid_size = 30 _grid_size_course = 300 _background_color = QtGui.QColor(50, 50, 50) _grid_penS = QtGui.QPen(QtGui.QColor(44, 44, 44, 255), 0.5) _grid_penL = QtGui.QPen(QtGui.QColor(40, 40, 40, 255), 1.0) _mouse_wheel_zoom_rate = 0.0005 _snap_to_grid = False def __init__(self, parent=None): super(GraphView, self).__init__(parent) self._parent = parent self.setRenderHint(QtGui.QPainter.Antialiasing) self.setRenderHint(QtGui.QPainter.TextAntialiasing) self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.resize(self._size) self.setSceneRect(-self._size.width() * 0.5, -self._size.height() * 0.5, self._size.width(), self._size.height()) self.setAcceptDrops(True) self.refresh() @property def grid_size(self): """ Return grid size :return: int """ return self._grid_size @property def snap_to_grid(self): """ get snap to grid stat :return: bool """ return self._snap_to_grid def refresh(self): """ Refresh Gui with setting all necessary items :return: None """ # Set Graphics scene self.setScene(QtGui.QGraphicsScene()) self._connections = set() self._nodes = {} self._selection = set() self._manipulation_mode = 0 self._selection_rect = None def add_node(self, node, emit_signal=True): """ :param node to add: :param emit_signal: emit node_add signal :return: """ self.scene().addItem(node) self._nodes[node.name] = node node.name_changed.connect(self._on_node_name_changed) if emit_signal: self.node_added.emit(node) return node def select_node(self, node, clear_selection=False, emit_signal=True): """ To highlight the given node :param node: node to highlight :return: None """ prev_selection = [] if emit_signal: for n in self._selection: prev_selection.append(n) if clear_selection is True: self.clear_selection(emit_signal=False) if node in self._selection: raise IndexError("Node is already in selection!") node.set_selected(True) self._selection.add(node) if emit_signal: new_selection = [] for n in self._selection: new_selection.append(n) self.selection_changed.emit(prev_selection, new_selection) def deselect_node(self, node, emit_signal=True): """ To deselect the given node :param node: node given :return: None """ if node not in self._selection: raise IndexError("Node is not in selection!") prev_selection = [] if emit_signal: for n in self._selection: prev_selection.append(n) node.set_selected(False) self._selection.remove(node) if emit_signal: new_selection = [] for n in self._selection: new_selection.append(n) self.selection_changed.emit(prev_selection, new_selection) def clear_selection(self, emit_signal=True): """ Clear nodes selection :param emitSignal: emit signal :return: None """ prev_selection = [] if emit_signal: for node in self._selection: prev_selection.append(node) for node in self._selection: node.set_selected(False) self._selection.clear() if emit_signal and len(prev_selection) != 0: self.selection_changed.emit(prev_selection, []) def get_selected_nodes(self): """ Get all selected node :return: None """ return self._selection def delete_selected_nodes(self): """ Delete selected nodes :return: None """ self.begin_delete_selection.emit() selected_nodes = self.get_selected_nodes() names = "" for node in selected_nodes: node.disconnect_all_ports() self.remove_node(node) self.end_delete_selection.emit() def move_selected_nodes(self, delta, emit_signal=True): """ Move all selected nodes to delta provided :param delta: delta value :param emit_signal: emit signal stat :return: None """ for node in self._selection: node.translate(delta.x(), delta.y()) if emit_signal: self.selection_moved.emit(self._selection, delta) def end_move_selected_nodes(self, delta): """ Report end of moving selected nodes :param delta: :return: None """ self.end_selection_moved.emit(self._selection, delta) def _on_node_name_changed(self, oldname, newname): """ Slot connected on node name changed :param oldname: old node name :param newname: new node name :return: None """ if newname in self._nodes and self._nodes[oldname] != self._nodes[ newname]: raise Exception("New name collides with existing node.") node = self._nodes[oldname] self._nodes[newname] = node del self.__nodes[oldname] self.node_name_changed.emit(oldname, newname) def drawBackground(self, painter, rect): """ override to drawBackground method to draw custom background :param painter: painter object :param rect: view rectangle :return: None """ painter.fillRect(rect, self._background_color) left = int(rect.left()) - (int(rect.left()) % self._grid_size) top = int(rect.top()) - (int(rect.top()) % self._grid_size) # Draw horizontal fine lines grid_lines = [] painter.setPen(self._grid_penS) y = float(top) while y < float(rect.bottom()): grid_lines.append(QtCore.QLineF(rect.left(), y, rect.right(), y)) y += self._grid_size painter.drawLines(grid_lines) # Draw vertical lines grid_lines = [] painter.setPen(self._grid_penL) x = float(left) while x < float(rect.right()): grid_lines.append(QtCore.QLineF(x, rect.top(), x, rect.bottom())) x += self._grid_size painter.drawLines(grid_lines) # Draw thick grid left = int(rect.left()) - (int(rect.left()) % self._grid_size_course) top = int(rect.top()) - (int(rect.top()) % self._grid_size_course) # Draw vertical thick lines grid_lines = [] painter.setPen(self._grid_penL) x = left while x < rect.right(): grid_lines.append(QtCore.QLineF(x, rect.top(), x, rect.bottom())) x += self._grid_size_course painter.drawLines(grid_lines) # Draw horizontal thick lines grid_lines = [] painter.setPen(self._grid_penL) y = top while y < rect.bottom(): grid_lines.append(QtCore.QLineF(rect.left(), y, rect.right(), y)) y += self._grid_size_course painter.drawLines(grid_lines) return super(GraphView, self).drawBackground(painter, rect) # ------------------------ # Connections # ------------------------ def emit_begin_connection_manipulation_signal(self): self.begin_connection_manipulation.emit() def emit_end_connection_manipulation_signal(self): self.end_connection_manipulation.emit() def add_connection(self, connection, emit_signal=True): """ to add new connection :param connection: new connection :param emit_signal: emit signal :return: connection """ self._connections.add(connection) self.scene().addItem(connection) if emit_signal: self.connection_added.emit(connection) return connection def remove_connection(self, connection, emit_signal=True): """ Remove existing connection :param connection: connection to remove :param emit_signal: emit signal ? :return:None """ connection.disconnect() self._connections.remove(connection) self.scene().removeItem(connection) if emit_signal: self.connection_removed.emit(connection) def get_node(self, name): """ Get node from node list :param name: name of the node :return: Node else None """ if name in self._nodes: return self._nodes[name] return None def connect_ports(self, src_node, output_name, tgt_node, input_name): """ Connect between ports :param src_node: source node :param output_name: outut name :param tgt_node: target node :param input_name: input name :return: Connection object """ if isinstance(src_node, Node): source_node = src_node elif isinstance(src_node, basestring): source_node = self.get_node(src_node) if not source_node: raise Exception("Node not found:" + str(src_node)) else: raise Exception("Invalid src_node:" + str(src_node)) source_port = source_node.get_port(output_name) if not source_port: raise Exception("Node '" + source_node.getName() + "' does not have output:" + output_name) if isinstance(tgt_node, Node): target_node = tgt_node elif isinstance(tgt_node, basestring): target_node = self.get_node(tgt_node) if not target_node: raise Exception("Node not found:" + str(tgt_node)) else: raise Exception("Invalid tgt_node:" + str(tgt_node)) target_port = target_node.getPort(input_name) if not target_port: raise Exception("Node '" + target_node.name() + "' does not have input:" + input_name) connection = Connection(self, source_port.out_circle, target_port.in_circle) self.add_connection(connection, emitSignal=False) return connection # -------------------------- # Events # -------------------------- def mousePressEvent(self, event): if event.button() == QtCore.Qt.LeftButton and self.itemAt( event.pos()) is None: self.begin_node_selection.emit() self._manipulation_mode = 1 self._mouse_down_selection = copy.copy(self.get_selected_nodes()) self.clear_selection(emit_signal=False) self._selection_rect = SelectionRect( graph=self, mouse_down_pos=self.mapToScene(event.pos())) elif event.button() is QtCore.Qt.MidButton: self.setCursor(QtCore.Qt.OpenHandCursor) self._manipulation_mode = 2 self._last_pan_point = self.mapToScene(event.pos()) else: super(GraphView, self).mousePressEvent(event) def mouseMoveEvent(self, event): if self._manipulation_mode == 1: drag_point = self.mapToScene(event.pos()) self._selection_rect.set_drag_point(drag_point) for name, node in self._nodes.iteritems(): if not node.is_selected( ) and self._selection_rect.collidesWithItem(node): self.select_node(node, emit_signal=False) elif self._manipulation_mode == 2: delta = self.mapToScene(event.pos()) - self._last_pan_point rect = self.sceneRect() rect.translate(-delta.x(), -delta.y()) self.setSceneRect(rect) self._last_pan_point = self.mapToScene(event.pos()) elif self._manipulation_mode == 3: new_pos = self.mapToScene(event.pos()) delta = new_pos - self._last_drag_point self._last_drag_point = new_pos selected_nodes = self.get_selected_nodes() # Apply the delta to each selected node for node in selected_nodes: node.translate(delta.x(), delta.y()) else: super(GraphView, self).mouseMoveEvent(event) def mouseReleaseEvent(self, event): if self._manipulation_mode == 1: self._selection_rect.destroy() self._selection_rect = None self._manipulation_mode = 0 selection = self.get_selected_nodes() deselected_nodes = [] selected_nodes = [] for node in self._mouse_down_selection: if node not in selection: deselected_nodes.append(node) for node in selection: if node not in self._mouse_down_selection: selected_nodes.append(node) if selected_nodes != deselected_nodes: self.selection_changed.emit(deselected_nodes, selected_nodes) self.end_node_selection.emit() elif self._manipulation_mode == 2: self.setCursor(QtCore.Qt.ArrowCursor) self._manipulation_mode = 0 else: super(GraphView, self).mouseReleaseEvent(event) def wheelEvent(self, event): (xfo, inv_res) = self.transform().inverted() top_left = xfo.map(self.rect().topLeft()) bottom_right = xfo.map(self.rect().bottomRight()) center = (top_left + bottom_right) * 0.5 zoom_factor = 1.0 + event.delta() * self._mouse_wheel_zoom_rate transform = self.transform() # Limit zoom to 3x if transform.m22() * zoom_factor >= 2.0: return self.scale(zoom_factor, zoom_factor) # Call udpate to redraw background self.update()