Example #1
0
 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]
Example #3
0
    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)
Example #4
0
    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)
Example #5
0
 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)
Example #6
0
    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)
Example #7
0
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)
Example #8
0
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)
Example #9
0
class GraphView(QtWidgets.QGraphicsView):

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

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

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

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

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

    _clipboardData = None

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

    _mouseWheelZoomRate = 0.0005

    _snapToGrid = False

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

        self.__graphViewWidget = parent

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

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

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

        self.setAcceptDrops(True)
        self.reset()

    def getGraphViewWidget(self):
        return self.__graphViewWidget

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

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

        self._manipulationMode = MANIP_MODE_NONE
        self._selectionRect = None

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

        Returns:
            int: Size of the grid.

        """

        return self._gridSizeFine

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

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

        """

        return self._snapToGrid

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

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

        """

        self._snapToGrid = snap

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

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

        if emitSignal:
            self.nodeAdded.emit(node)

        return node

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

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

        if emitSignal:
            self.nodeRemoved.emit(node)

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

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

    def getNodes(self):
        return self.__nodes

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

    def clearSelection(self, emitSignal=True):

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

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

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

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

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

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

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

        if emitSignal:

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

            self.selectionChanged.emit(prevSelection, newSelection)

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

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

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

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

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

            self.selectionChanged.emit(prevSelection, newSelection)

    def getSelectedNodes(self):
        return self.__selection

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

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

        self.endDeleteSelection.emit()

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

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

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

        windowRect = computeWindowFrame()

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

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

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

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

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

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

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

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

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

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

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

        return pos

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

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

            # After moving the nodes interactively, this signal is emitted with the final delta.

    def endMoveSelectedNodes(self, delta):
        self.endSelectionMoved.emit(self.__selection, delta)

    ################################################
    ## 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
Example #10
0
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()
Example #11
0
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)
Example #12
0
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()