Example #1
0
    def __init__(
                    self, 
                    parent=None, 
                    engine=None, 
                    show_subgraphs = True,
                    manipulation_mode=QGraphVizManipulationMode.Nodes_Move_Mode,
                    new_edge_created_callback=None, # A callbakc called when a new connection is created between two nodes using the GUI
                    node_selected_callback=None, # A callback called when a node is clicked
                    edge_selected_callback=None, # A callback called when an edge is clicked
                    node_invoked_callback=None, # A callback called when a node is double clicked
                    edge_invoked_callback=None, # A callback called when an edge is double clicked
                ):
        QWidget.__init__(self,parent)
        self.parser = DotParser()
        self.engine=engine
        self.qnodes=[]
        self.qedges=[]
        # Nodes manipulation
        self.manipulation_mode = manipulation_mode
        self.selected_Node = None  
        self.current_pos = [0,0]
        self.mouse_down=False
        self.min_cursor_edge_dist=3
        self.show_subgraphs=show_subgraphs

        self.new_edge_created_callback = new_edge_created_callback
        self.node_selected_callback = node_selected_callback
        self.edge_selected_callback = edge_selected_callback
        self.node_invoked_callback = node_invoked_callback
        self.edge_invoked_callback = edge_invoked_callback
    def __init__(
            self,
            parent=None,
            engine=None,
            show_subgraphs=True,
            manipulation_mode=QGraphVizManipulationMode.Nodes_Move_Mode,
            # Callbacks
            new_edge_beingAdded_callback=None,  # A callback called when a new connection is being added (should return True or False to accept or not the edge, as well as return the edge parameters)
            new_edge_created_callback=None,  # A callbakc called when a new connection is created between two nodes using the GUI
            node_selected_callback=None,  # A callback called when a node is clicked
            edge_selected_callback=None,  # A callback called when an edge is clicked
            node_invoked_callback=None,  # A callback called when a node is double clicked
            edge_invoked_callback=None,  # A callback called when an edge is double clicked
            node_removed_callback=None,  # A callback called when a node is removed
            edge_removed_callback=None,  # A callback called when an edge is removed

            # Custom options
        min_cursor_edge_dist=3,
            hilight_Nodes=False,
            hilight_Edges=False):
        """
        QGraphViz widget Constructor
        :param parent: A QWidget parent of the QGraphViz widget
        :param engine: The graph processing engine (exemple Dot engine)
        :param show_subgraphs: Tells whether to show the content of subgraphs or not
        :param manipulation_mode: Sets the current graph manipulations mode
        :param new_edge_beingAdded_callback: A callback issued when a new edge is being added. This callback should return a boolean to accept or refuse adding the edge.
        :param new_edge_created_callback: A callback issued when a new edge is added.
        :param node_selected_callback: A callback issued when a node is selected.
        :param edge_selected_callback: A callback issued when an edge is selected.
        :param node_removed_callback: A callback issued when an node is removed.
        :param edge_removed_callback: A callback issued when an edge is removed.
        :param min_cursor_edge_dist: Minimal distance between sursor edge.
        :param hilight_Nodes: If True, whenever mouse is hovered on a node, it is hilighted.
        :param hilight_Edges: If True, whenever mouse is hovered on an edge, it is hilighted.
        """
        QWidget.__init__(self, parent)
        self.parser = DotParser()
        self.engine = engine

        # Pfrepare lists
        self.qnodes = []
        self.qedges = []

        # Nodes manipulation
        self.manipulation_mode = manipulation_mode
        self.selected_Node = None
        self.hovered_Node = None
        self.hovered_Edge = None
        self.hovered_Edge_id = None
        self.current_pos = [0, 0]
        self.mouse_down = False
        self.min_cursor_edge_dist = min_cursor_edge_dist
        self.show_subgraphs = show_subgraphs

        # Set callbacks
        self.new_edge_beingAdded_callback = new_edge_beingAdded_callback
        self.new_edge_created_callback = new_edge_created_callback
        self.node_selected_callback = node_selected_callback
        self.edge_selected_callback = edge_selected_callback
        self.node_invoked_callback = node_invoked_callback
        self.edge_invoked_callback = edge_invoked_callback
        self.node_removed_callback = node_removed_callback
        self.edge_removed_callback = edge_removed_callback

        self.hilight_Nodes = hilight_Nodes
        self.hilight_Edges = hilight_Edges

        self.setAutoFillBackground(True)
        self.setAttribute(Qt.WA_StyledBackground, True)
        self.setMouseTracking(True)
class QGraphViz(QWidget):
    """
    Main graphviz widget to draw and interact with graphs
    """
    def __init__(
            self,
            parent=None,
            engine=None,
            show_subgraphs=True,
            manipulation_mode=QGraphVizManipulationMode.Nodes_Move_Mode,
            # Callbacks
            new_edge_beingAdded_callback=None,  # A callback called when a new connection is being added (should return True or False to accept or not the edge, as well as return the edge parameters)
            new_edge_created_callback=None,  # A callbakc called when a new connection is created between two nodes using the GUI
            node_selected_callback=None,  # A callback called when a node is clicked
            edge_selected_callback=None,  # A callback called when an edge is clicked
            node_invoked_callback=None,  # A callback called when a node is double clicked
            edge_invoked_callback=None,  # A callback called when an edge is double clicked
            node_removed_callback=None,  # A callback called when a node is removed
            edge_removed_callback=None,  # A callback called when an edge is removed

            # Custom options
        min_cursor_edge_dist=3,
            hilight_Nodes=False,
            hilight_Edges=False):
        """
        QGraphViz widget Constructor
        :param parent: A QWidget parent of the QGraphViz widget
        :param engine: The graph processing engine (exemple Dot engine)
        :param show_subgraphs: Tells whether to show the content of subgraphs or not
        :param manipulation_mode: Sets the current graph manipulations mode
        :param new_edge_beingAdded_callback: A callback issued when a new edge is being added. This callback should return a boolean to accept or refuse adding the edge.
        :param new_edge_created_callback: A callback issued when a new edge is added.
        :param node_selected_callback: A callback issued when a node is selected.
        :param edge_selected_callback: A callback issued when an edge is selected.
        :param node_removed_callback: A callback issued when an node is removed.
        :param edge_removed_callback: A callback issued when an edge is removed.
        :param min_cursor_edge_dist: Minimal distance between sursor edge.
        :param hilight_Nodes: If True, whenever mouse is hovered on a node, it is hilighted.
        :param hilight_Edges: If True, whenever mouse is hovered on an edge, it is hilighted.
        """
        QWidget.__init__(self, parent)
        self.parser = DotParser()
        self.engine = engine

        # Pfrepare lists
        self.qnodes = []
        self.qedges = []

        # Nodes manipulation
        self.manipulation_mode = manipulation_mode
        self.selected_Node = None
        self.hovered_Node = None
        self.hovered_Edge = None
        self.hovered_Edge_id = None
        self.current_pos = [0, 0]
        self.mouse_down = False
        self.min_cursor_edge_dist = min_cursor_edge_dist
        self.show_subgraphs = show_subgraphs

        # Set callbacks
        self.new_edge_beingAdded_callback = new_edge_beingAdded_callback
        self.new_edge_created_callback = new_edge_created_callback
        self.node_selected_callback = node_selected_callback
        self.edge_selected_callback = edge_selected_callback
        self.node_invoked_callback = node_invoked_callback
        self.edge_invoked_callback = edge_invoked_callback
        self.node_removed_callback = node_removed_callback
        self.edge_removed_callback = edge_removed_callback

        self.hilight_Nodes = hilight_Nodes
        self.hilight_Edges = hilight_Edges

        self.setAutoFillBackground(True)
        self.setAttribute(Qt.WA_StyledBackground, True)
        self.setMouseTracking(True)

    def build(self):
        self.engine.build()
        """
        for node in self.engine.graph.nodes:
            qnode = QNode(node, self)
            qnode.setParent(self)
            self.qnodes.append(qnode)
        for edge in self.engine.graph.edges:
            qedge = QEdge(edge, self)
            qedge.setParent(self)
            self.qedges.append(qedge)
        """

    def paintSubgraph(self, subgraph, painter, pen, brush):
        if ("color" in subgraph.kwargs.keys()):
            pen.setColor(QColor(subgraph.kwargs["color"]))
        else:
            pen.setColor(QColor("black"))

        if ("fillcolor" in subgraph.kwargs.keys()):
            if (":" in subgraph.kwargs["fillcolor"]):
                gradient = QLinearGradient(
                    subgraph.pos[0] - subgraph.size[0] / 2, subgraph.pos[1],
                    subgraph.pos[0] + subgraph.size[0] / 2, subgraph.pos[1])
                c = subgraph.kwargs["fillcolor"].split(":")
                for i, col in enumerate(c):
                    stop = i / (len(c) - 1)
                    gradient.setColorAt(stop, QColor(col))

                brush = QBrush(gradient)
            else:
                brush = QBrush(QColor(subgraph.kwargs["fillcolor"]))
        else:
            brush = QBrush(QColor("white"))

        if ("width" in subgraph.kwargs.keys()):
            pen.setWidth(int(subgraph.kwargs["width"]))
        else:
            pen.setWidth(1)

        painter.setPen(pen)
        painter.setBrush(brush)
        gpos = subgraph.global_pos

        painter.drawRect(gpos[0] - subgraph.size[0] / 2,
                         gpos[1] - subgraph.size[1] / 2, subgraph.size[0],
                         subgraph.size[1])

        if ("label" in subgraph.kwargs.keys()):
            painter.drawText(gpos[0] - subgraph.size[0] / 2,
                             gpos[1] - subgraph.size[1] / 2, subgraph.size[0],
                             subgraph.size[1], Qt.AlignCenter | Qt.AlignTop,
                             subgraph.kwargs["label"])

    def paintGraph(self, graph, painter):
        brush = QBrush(Qt.SolidPattern)
        pen = QPen()
        brush.setColor(Qt.white)

        if (self.show_subgraphs):
            for node in graph.nodes:
                if type(node) == Graph:
                    subgraph = node
                    self.paintSubgraph(subgraph, painter, pen, brush)

        for i, edge in enumerate(graph.edges):
            if ("color" in edge.kwargs.keys()):
                pen.setColor(QColor(edge.kwargs["color"]))
            else:
                pen.setColor(QColor("black"))

            if ("width" in edge.kwargs.keys()):
                pen.setWidth(int(edge.kwargs["width"]))
            else:
                pen.setWidth(1)

            painter.setPen(pen)
            painter.setBrush(brush)
            if (edge.source.parent_graph != graph and not self.show_subgraphs):
                gspos = edge.source.parent_graph.global_pos
            else:
                gspos = edge.source.global_pos

            if (edge.dest.parent_graph != graph and not self.show_subgraphs):
                gspos = edge.dest.parent_graph.global_pos
            else:
                gdpos = edge.dest.global_pos

            nb_next = 0
            for j in range(i, len(graph.edges)):
                if (graph.edges[j].source == edge.source
                        and graph.edges[j].dest == edge.dest):
                    nb_next += 1

            offset = [0, 0]
            if (nb_next % 2 == 1):
                offset[0] = 20 * (nb_next / 2)
            else:
                offset[0] = -20 * (nb_next / 2)
            path = QPainterPath()
            path.moveTo(gspos[0], gspos[1])
            path.cubicTo(gspos[0], gspos[1],
                         offset[0] + (gspos[0] + gdpos[0]) / 2,
                         (gspos[1] + gdpos[1]) / 2, gdpos[0], gdpos[1])
            painter.strokePath(path, pen)
            """
            painter.drawLine(gspos[0],gspos[1],
            gdpos[0],
            gdpos[1])
            """
        # TODO : add more painting parameters
        for node in graph.nodes:
            if type(node) != Graph:
                if ("color" in node.kwargs.keys()):
                    pen.setColor(QColor(node.kwargs["color"]))
                else:
                    pen.setColor(QColor("black"))

                if ("fillcolor" in node.kwargs.keys()):
                    if (":" in node.kwargs["fillcolor"]):
                        gradient = QLinearGradient(
                            node.pos[0] - node.size[0] / 2, node.pos[1],
                            node.pos[0] + node.size[0] / 2, node.pos[1])
                        c = node.kwargs["fillcolor"].split(":")
                        for i, col in enumerate(c):
                            stop = i / (len(c) - 1)
                            gradient.setColorAt(stop, QColor(col))

                        brush = QBrush(gradient)
                    else:
                        brush = QBrush(QColor(node.kwargs["fillcolor"]))
                else:
                    brush = QBrush(QColor("white"))

                if ("width" in node.kwargs.keys()):
                    pen.setWidth(int(node.kwargs["width"]))
                else:
                    pen.setWidth(1)

                gpos = node.global_pos

                painter.setPen(pen)
                painter.setBrush(brush)
                if ("shape" in node.kwargs.keys()):
                    if (node.kwargs["shape"] == "box"):
                        painter.drawRect(gpos[0] - node.size[0] / 2,
                                         gpos[1] - node.size[1] / 2,
                                         node.size[0], node.size[1])

                    if (node.kwargs["shape"] == "circle"):
                        painter.drawEllipse(gpos[0] - node.size[0] / 2,
                                            gpos[1] - node.size[1] / 2,
                                            node.size[0], node.size[1])

                    # Image as a node, this implementation checks to see if a
                    # file path was provided in the shape parameter
                    if (os.path.isfile(node.kwargs["shape"])):
                        img_path = node.kwargs["shape"]
                        painter.drawImage(
                            QRect(gpos[0] - node.size[0] / 2,
                                  gpos[1] - node.size[1] / 2, node.size[0],
                                  node.size[1]), QImage(img_path))
                else:
                    painter.drawEllipse(gpos[0] - node.size[0] / 2,
                                        gpos[1] - node.size[1] / 2,
                                        node.size[0], node.size[1])

                if ("label" in node.kwargs.keys()):
                    painter.drawText(gpos[0] - node.size[0] / 2,
                                     gpos[1] - node.size[1] / 2, node.size[0],
                                     node.size[1],
                                     Qt.AlignCenter | Qt.AlignTop,
                                     node.kwargs["label"])
            else:
                if (self.show_subgraphs):
                    self.paintGraph(subgraph, painter)
                else:
                    subgraph = node
                    self.paintSubgraph(subgraph, painter, pen, brush)

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setFont(self.engine.font)
        self.paintGraph(self.engine.graph, painter)
        if (self.manipulation_mode
                == QGraphVizManipulationMode.Edges_Connect_Mode
                and self.mouse_down and self.selected_Node is not None):
            bkp = painter.pen()
            pen = QPen(Qt.DashLine)
            painter.setPen(pen)
            painter.drawLine(self.selected_Node.pos[0],
                             self.selected_Node.pos[1], self.current_pos[0],
                             self.current_pos[1])
            painter.setPen(bkp)
        painter.end()

    def new(self, engine):
        """
        Creates a new engine
        :param engine: An engine object (for example a Dot engine)
        """
        self.engine = engine

    def addNode(self, graph, node_name, **kwargs):
        """
        Adds a node to a graph or subgraph
        """
        node = Node(node_name, graph, **kwargs)
        graph.nodes.append(node)
        return node

    def addEdge(self, source, dest, kwargs):
        """
        Connects two nodes from the same subgraph or 
        from two different subgraphs
        If source and dest nodes belong to the same
        Subgraph, the connection added to the subgraph
        if the connection is between different subgraph notes
        the connection is added to the main subgraph 
        """
        edge = Edge(source, dest)
        edge.kwargs = kwargs
        if (source.parent_graph == dest.parent_graph):
            source.parent_graph.edges.append(edge)
        else:
            self.engine.graph.edges.append(edge)

        return edge

    def addSubgraph(self,
                    parent_graph,
                    subgraph_name,
                    subgraph_type=GraphType.SimpleGraph,
                    **kwargs):
        subgraph = Graph(subgraph_name, subgraph_type, parent_graph, **kwargs)
        subgraph.name = subgraph_name
        subgraph.parent_graph = parent_graph
        parent_graph.nodes.append(subgraph)
        return subgraph

    def removeNode(self, node):
        graph = node.parent_graph
        if (node in graph.nodes):
            idx = graph.nodes.index(node)
            node = graph.nodes[idx]
            if (self.node_removed_callback is not None):
                self.node_removed_callback(node)
            for edge in node.in_edges:
                del edge.source.out_edges[edge.source.out_edges.index(edge)]
                if edge.source.parent_graph == edge.dest.parent_graph:
                    del edge.source.parent_graph.edges[
                        edge.source.parent_graph.edges.index(edge)]
                else:
                    del self.engine.graph.edges[self.engine.graph.edges.index(
                        edge)]

            for edge in node.out_edges:
                del edge.source.out_edges[edge.source.out_edges.index(edge)]
                if edge.source.parent_graph == edge.dest.parent_graph:
                    del edge.source.parent_graph.edges[
                        edge.source.parent_graph.edges.index(edge)]
                else:
                    del self.engine.graph.edges[self.engine.graph.edges.index(
                        edge)]
            del graph.nodes[idx]
            self.repaint()

    def removeSubgraph(self, subgraph):
        graph = subgraph.parent_graph
        if (subgraph in graph.subgraphs):
            idx = graph.subgraphs.index(subgraph)
            subgraph = graph.subgraphs[idx]
            if (self.node_removed_callback is not None):
                self.node_removed_callback(subgraph)
            del graph.subgraphs[idx]
            self.repaint()

    def removeEdge(self, edge):
        if (edge in self.engine.graph.edges):
            source = edge.source
            dest = edge.dest
            if (self.edge_removed_callback is not None):
                self.edge_removed_callback(edge)

            idx = source.out_edges.index(edge)
            del source.out_edges[idx]

            idx = dest.in_edges.index(edge)
            del dest.in_edges[idx]

            if edge.source.parent_graph == edge.dest.parent_graph:
                del edge.source.parent_graph.edges[
                    edge.source.parent_graph.edges.index(edge)]
            else:
                del self.engine.graph.edges[self.engine.graph.edges.index(
                    edge)]

            self.repaint()

    def findSubNode(self, graph, x, y):
        for node in graph.nodes:
            gpos = node.global_pos
            if (type(node) == Graph and gpos[0] - node.size[0] / 2 < x
                    and gpos[0] + node.size[0] / 2 > x
                    and gpos[1] - node.size[1] / 2 < y
                    and gpos[1] + node.size[1] / 2 > y):
                return node
        return None

    def isNodeHovered(self, n, x, y):
        gpos = n.global_pos
        if (gpos[0] - n.size[0] / 2 < x and gpos[0] + n.size[0] / 2 > x
                and gpos[1] - n.size[1] / 2 < y
                and gpos[1] + n.size[1] / 2 > y):
            return True
        else:
            return False

    def isEdgeHovered(self, graph, i, e, x, y):
        nb_next = 0
        for j in range(i, len(graph.edges)):
            if (graph.edges[j].source == e.source
                    and graph.edges[j].dest == e.dest):
                nb_next += 1

        offset = [0, 0]
        if (nb_next % 2 == 1):
            offset[0] = 20 * (nb_next / 2)
        else:
            offset[0] = -20 * (nb_next / 2)

        sx = e.source.pos[
            0] if e.source.pos[0] < e.dest.pos[0] else e.dest.pos[0]
        sy = e.source.pos[
            1] if e.source.pos[1] < e.dest.pos[1] else e.dest.pos[1]

        ex = e.source.pos[
            0] if e.source.pos[0] > e.dest.pos[0] else e.dest.pos[0]
        ey = e.source.pos[
            1] if e.source.pos[1] > e.dest.pos[1] else e.dest.pos[1]

        sx += +offset[0]
        ex += +offset[0]

        if (x > sx - self.min_cursor_edge_dist
                and x < ex + self.min_cursor_edge_dist
                and y > sy - self.min_cursor_edge_dist
                and y < ey + self.min_cursor_edge_dist):
            x2 = x - sx
            y2 = y - sy
            dx = (ex - sx)
            dy = (ey - sy)
            if (dx == 0):
                if (abs(x2) < self.min_cursor_edge_dist):
                    return True
            elif (dy == 0):
                if (abs(y2) < self.min_cursor_edge_dist):
                    return True
            else:
                a = -dy / dx
                if (abs(a * x2 + y2) / math.sqrt(a**2) <
                        self.min_cursor_edge_dist):
                    return True
        return False

    def findNode(self, graph, x, y):
        for n in graph.nodes:
            if (self.isNodeHovered(n, x, y)):
                return n
        return None

    def findEdge(self, graph, x, y):
        for i, e in enumerate(graph.edges):
            if (self.isEdgeHovered(graph, i, e, x, y)):
                return e, i
        return None, 0

    def load_file(self, filename):
        self.engine.graph = self.parser.parseFile(filename)
        self.build()
        self.update()

    def loadAJson(self, filename):
        self.engine.graph = self.parser.fromJSON(filename)
        self.build()
        self.update()

    def save(self, filename):
        self.parser.save(filename, self.engine.graph)

    def saveAsJson(self, filename):
        self.parser.toJSON(filename, self.engine.graph)

    def mouseDoubleClickEvent(self, event):
        x = event.x()
        y = event.y()
        n = self.findNode(self.engine.graph, x, y)
        if n is not None:
            if (self.node_invoked_callback is not None):
                self.node_invoked_callback(n)
        else:
            e, _ = self.findEdge(self.engine.graph, x, y)
            if e is not None:
                if (self.edge_invoked_callback is not None):
                    self.edge_invoked_callback(e)

        QWidget.mouseDoubleClickEvent(self, event)
        self.leaveEvent()

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            x = event.x()
            y = event.y()
            self.current_pos = [x, y]
            self.mouse_down = True
            n = self.findNode(self.engine.graph, x, y)
            self.selected_Node = n

            if (n is None):
                n = self.findSubNode(self.engine.graph, x, y)
                self.selected_Node = n

        QWidget.mousePressEvent(self, event)

    def leaveEvent(self, event=None):
        """
        Used to reset some parameters when the mouse leaves the QWidget
        """
        self.selected_Node = None

        self.mouse_down = False

        if (self.hovered_Node is not None):
            self.hovered_Node.kwargs["width"] = self.hovered_Node_Back_width
            self.hovered_Node = None

        if (self.hovered_Edge is not None):
            self.hovered_Edge.kwargs["width"] = self.hovered_Edge_Back_width
            self.hovered_Edge = None

        self.update()
        if (event != None):
            event.accept()

    def mouseMoveEvent(self, event):
        if self.selected_Node is not None and self.mouse_down:
            x = event.x()
            y = event.y()
            if (self.manipulation_mode ==
                    QGraphVizManipulationMode.Nodes_Move_Mode):
                self.selected_Node.pos[0] += x - self.current_pos[0]
                self.selected_Node.pos[1] += y - self.current_pos[1]

            self.current_pos = [x, y]
            self.repaint()
        else:
            x = event.x()
            y = event.y()
            if (self.hilight_Nodes):
                if (self.hovered_Node is None):
                    self.hovered_Node = self.findNode(self.engine.graph, x, y)
                    if (self.hovered_Node is not None):
                        if "width" in list(self.hovered_Node.kwargs.keys()):
                            self.hovered_Node_Back_width = self.hovered_Node.kwargs[
                                "width"]
                        else:
                            self.hovered_Node_Back_width = 1
                        self.hovered_Node.kwargs[
                            "width"] = self.hovered_Node_Back_width + 3
                        self.update()
                else:
                    if not (self.isNodeHovered(self.hovered_Node, x, y)):
                        self.hovered_Node.kwargs[
                            "width"] = self.hovered_Node_Back_width
                        self.hovered_Node = None
                        self.update()
            if (self.hilight_Edges):
                if (self.hovered_Edge is None):
                    self.hovered_Edge, self.hovered_Edge_id = self.findEdge(
                        self.engine.graph, x, y)
                    if (self.hovered_Edge is not None):
                        if "width" in list(self.hovered_Edge.kwargs.keys()):
                            self.hovered_Edge_Back_width = self.hovered_Edge.kwargs[
                                "width"]
                        else:
                            self.hovered_Edge_Back_width = 1
                        self.hovered_Edge.kwargs[
                            "width"] = self.hovered_Edge_Back_width + 3
                        self.update()
                else:
                    if not (self.isEdgeHovered(self.engine.graph,
                                               self.hovered_Edge_id,
                                               self.hovered_Edge, x, y)):
                        self.hovered_Edge.kwargs[
                            "width"] = self.hovered_Edge_Back_width
                        self.hovered_Edge = None
                        self.update()

        QWidget.mouseMoveEvent(self, event)

    def mouseReleaseEvent(self, event):
        x = event.x()
        y = event.y()
        n = self.findNode(self.engine.graph, x, y)
        if n is None:
            s = self.findSubNode(self.engine.graph, x, y)
        if n is None:
            e, _ = self.findEdge(self.engine.graph, x, y)
        else:
            e = None
        # Manipulating nodes
        if (self.manipulation_mode == QGraphVizManipulationMode.Nodes_Move_Mode
            ):
            if self.selected_Node is not None and self.mouse_down:
                selected_Node = self.selected_Node
                s = self.findSubNode(self.engine.graph, x, y)
                if (s is not None and s != selected_Node):
                    if (type(selected_Node) == Node):
                        del selected_Node.parent_graph.nodes[
                            selected_Node.parent_graph.nodes.index(
                                selected_Node)]
                        s.nodes.append(selected_Node)
                        selected_Node.parent_graph = s
                        self.build()
                        self.repaint()
                    if (type(selected_Node) == Graph):
                        del selected_Node.parent_graph.nodes[
                            selected_Node.parent_graph.nodes.index(
                                selected_Node)]
                        s.nodes.append(selected_Node)
                        selected_Node.parent_graph = s
                        self.build()
                        self.repaint()

        # Connecting edges
        if (self.manipulation_mode ==
                QGraphVizManipulationMode.Edges_Connect_Mode):
            if self.selected_Node is not None and self.mouse_down:
                selected_Node = self.selected_Node
                d = n if n is not None else s
                if (d != selected_Node and d is not None):
                    add_the_edge = True
                    if (self.new_edge_beingAdded_callback is not None):
                        add_the_edge, kwargs = self.new_edge_beingAdded_callback(
                            selected_Node, d)
                    else:
                        kwargs = {}
                    if add_the_edge:
                        edge = self.addEdge(selected_Node, d, kwargs)
                        if (add_the_edge):
                            if (self.new_edge_created_callback is not None):
                                self.new_edge_created_callback(edge)
                        self.build()
                self.selected_Node = None
        # Removing node
        elif (self.manipulation_mode ==
              QGraphVizManipulationMode.Node_remove_Mode):
            if (n is not None):
                self.removeNode(n)
                self.build()
                self.repaint()

        #Removing edge
        elif (self.manipulation_mode ==
              QGraphVizManipulationMode.Edge_remove_Mode):
            if (e is not None):
                self.removeEdge(e)
                self.build()
                self.repaint()
        # Remiving Subgraph
        elif (self.manipulation_mode ==
              QGraphVizManipulationMode.Subgraph_remove_Mode):
            if (s is not None):
                self.removeSubgraph(s)
                self.build()
                self.repaint()

        # Inform application
        if (n is not None):
            if (self.node_selected_callback is not None):
                self.node_selected_callback(n)

        if (e is not None):
            if (self.edge_selected_callback is not None):
                self.edge_selected_callback(e)

        QWidget.mouseReleaseEvent(self, event)
        self.mouse_down = False
        self.repaint()
Example #4
0
class QGraphViz(QWidget):
    """
    Main graphviz widget to draw and interact with graphs
    :param parent: A QWidget parent of the QGraphViz widget
    :param engine: The graph processing engine (exemple Dot engine)
    """
    def __init__(
        self,
        parent=None,
        engine=None,
        show_subgraphs=True,
        manipulation_mode=QGraphVizManipulationMode.Nodes_Move_Mode,
        new_edge_beingAdded_callback=None,  # A callback called when a new connection is being added (should return True or False to accept or not the edge, as well as return the edge parameters)
        new_edge_created_callback=None,  # A callbakc called when a new connection is created between two nodes using the GUI
        node_selected_callback=None,  # A callback called when a node is clicked
        edge_selected_callback=None,  # A callback called when an edge is clicked
        node_invoked_callback=None,  # A callback called when a node is double clicked
        edge_invoked_callback=None,  # A callback called when an edge is double clicked
        node_removed_callback=None,  # A callback called when a node is removed
        edge_removed_callback=None  # A callback called when an edge is removed
    ):
        QWidget.__init__(self, parent)
        self.parser = DotParser()
        self.engine = engine
        self.qnodes = []
        self.qedges = []
        # Nodes manipulation
        self.manipulation_mode = manipulation_mode
        self.selected_Node = None
        self.current_pos = [0, 0]
        self.mouse_down = False
        self.min_cursor_edge_dist = 3
        self.show_subgraphs = show_subgraphs

        self.new_edge_beingAdded_callback = new_edge_beingAdded_callback
        self.new_edge_created_callback = new_edge_created_callback
        self.node_selected_callback = node_selected_callback
        self.edge_selected_callback = edge_selected_callback
        self.node_invoked_callback = node_invoked_callback
        self.edge_invoked_callback = edge_invoked_callback
        self.node_removed_callback = node_removed_callback
        self.edge_removed_callback = edge_removed_callback  # A callback called when an edge is removed

    def build(self):
        self.engine.build()
        """
        for node in self.engine.graph.nodes:
            qnode = QNode(node, self)
            qnode.setParent(self)
            self.qnodes.append(qnode)
        for edge in self.engine.graph.edges:
            qedge = QEdge(edge, self)
            qedge.setParent(self)
            self.qedges.append(qedge)
        """

    def paintSubgraph(self, subgraph, painter, pen, brush):
        if ("color" in subgraph.kwargs.keys()):
            pen.setColor(QColor(subgraph.kwargs["color"]))
        else:
            pen.setColor(QColor("black"))

        painter.setPen(pen)
        painter.setBrush(brush)
        gpos = subgraph.global_pos

        painter.drawRect(gpos[0] - subgraph.size[0] / 2,
                         gpos[1] - subgraph.size[1] / 2, subgraph.size[0],
                         subgraph.size[1])

        if ("label" in subgraph.kwargs.keys()):
            painter.drawText(gpos[0] - subgraph.size[0] / 2,
                             gpos[1] - subgraph.size[1] / 2, subgraph.size[0],
                             subgraph.size[1], Qt.AlignCenter | Qt.AlignTop,
                             subgraph.kwargs["label"])

    def paintGraph(self, graph, painter):
        brush = QBrush(Qt.SolidPattern)
        pen = QPen()
        brush.setColor(Qt.white)

        if (self.show_subgraphs):
            for node in graph.nodes:
                if type(node) == Graph:
                    subgraph = node
                    self.paintSubgraph(subgraph, painter, pen, brush)

        for i, edge in enumerate(graph.edges):
            if ("color" in edge.kwargs.keys()):
                pen.setColor(QColor(edge.kwargs["color"]))
            else:
                pen.setColor(QColor("black"))

            if ("width" in edge.kwargs.keys()):
                pen.setWidth(int(edge.kwargs["width"]))
            else:
                pen.setWidth(1)

            painter.setPen(pen)
            painter.setBrush(brush)
            if (edge.source.parent_graph != graph and not self.show_subgraphs):
                gspos = edge.source.parent_graph.global_pos
            else:
                gspos = edge.source.global_pos

            if (edge.dest.parent_graph != graph and not self.show_subgraphs):
                gspos = edge.dest.parent_graph.global_pos
            else:
                gdpos = edge.dest.global_pos

            nb_next = 0
            for j in range(i, len(graph.edges)):
                if (graph.edges[j].source == edge.source
                        and graph.edges[j].dest == edge.dest):
                    nb_next += 1

            offset = [0, 0]
            if (nb_next % 2 == 1):
                offset[0] = 20 * (nb_next / 2)
            else:
                offset[0] = -20 * (nb_next / 2)
            path = QPainterPath()
            path.moveTo(gspos[0], gspos[1])
            path.cubicTo(gspos[0], gspos[1],
                         offset[0] + (gspos[0] + gdpos[0]) / 2,
                         (gspos[1] + gdpos[1]) / 2, gdpos[0], gdpos[1])
            painter.strokePath(path, pen)
            """
            painter.drawLine(gspos[0],gspos[1],
            gdpos[0],
            gdpos[1])
            """
        # TODO : add more painting parameters
        for node in graph.nodes:
            if type(node) != Graph:
                if ("color" in node.kwargs.keys()):
                    pen.setColor(QColor(node.kwargs["color"]))
                else:
                    pen.setColor(QColor("black"))
                gpos = node.global_pos

                painter.setPen(pen)
                painter.setBrush(brush)
                if ("shape" in node.kwargs.keys()):
                    if (node.kwargs["shape"] == "box"):
                        painter.drawRect(gpos[0] - node.size[0] / 2,
                                         gpos[1] - node.size[1] / 2,
                                         node.size[0], node.size[1])

                    if (node.kwargs["shape"] == "circle"):
                        painter.drawEllipse(gpos[0] - node.size[0] / 2,
                                            gpos[1] - node.size[1] / 2,
                                            node.size[0], node.size[1])

                else:
                    painter.drawEllipse(gpos[0] - node.size[0] / 2,
                                        gpos[1] - node.size[1] / 2,
                                        node.size[0], node.size[1])

                if ("label" in node.kwargs.keys()):
                    painter.drawText(gpos[0] - node.size[0] / 2,
                                     gpos[1] - node.size[1] / 2, node.size[0],
                                     node.size[1],
                                     Qt.AlignCenter | Qt.AlignTop,
                                     node.kwargs["label"])
            else:
                if (self.show_subgraphs):
                    self.paintGraph(subgraph, painter)
                else:
                    subgraph = node
                    self.paintSubgraph(subgraph, painter, pen, brush)

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setFont(self.engine.font)
        self.paintGraph(self.engine.graph, painter)
        if (self.manipulation_mode
                == QGraphVizManipulationMode.Edges_Connect_Mode
                and self.mouse_down and self.selected_Node is not None):
            bkp = painter.pen()
            pen = QPen(Qt.DashLine)
            painter.setPen(pen)
            painter.drawLine(self.selected_Node.pos[0],
                             self.selected_Node.pos[1], self.current_pos[0],
                             self.current_pos[1])
            painter.setPen(bkp)
        painter.end()

    def new(self, engine):
        """
        Creates a new engine
        :param engine: An engine object (for example a Dot engine)
        """
        self.engine = engine

    def addNode(self, graph, node_name, **kwargs):
        """
        Adds a node to a graph or subgraph
        """
        node = Node(node_name, graph, **kwargs)
        graph.nodes.append(node)
        return node

    def addEdge(self, source, dest, kwargs):
        """
        Connects two nodes from the same subgraph or 
        from two different subgraphs
        If source and dest nodes belong to the same
        Subgraph, the connection added to the subgraph
        if the connection is between different subgraph notes
        the connection is added to the main subgraph 
        """
        edge = Edge(source, dest)
        edge.kwargs = kwargs
        if (source.parent_graph == dest.parent_graph):
            source.parent_graph.edges.append(edge)
        else:
            self.engine.graph.edges.append(edge)

        return edge

    def addSubgraph(self,
                    parent_graph,
                    subgraph_name,
                    subgraph_type=GraphType.SimpleGraph,
                    **kwargs):
        subgraph = Graph(subgraph_name, subgraph_type, parent_graph, **kwargs)
        subgraph.name = subgraph_name
        subgraph.parent_graph = parent_graph
        parent_graph.nodes.append(subgraph)
        return subgraph

    def removeNode(self, node):
        graph = node.parent_graph
        if (node in graph.nodes):
            idx = graph.nodes.index(node)
            node = graph.nodes[idx]
            if (self.node_removed_callback is not None):
                self.node_removed_callback(node)
            for edge in node.in_edges:
                del edge.source.out_edges[edge.source.out_edges.index(edge)]
                if edge.source.parent_graph == edge.dest.parent_graph:
                    del edge.source.parent_graph.edges[
                        edge.source.parent_graph.edges.index(edge)]
                else:
                    del self.engine.graph.edges[self.engine.graph.edges.index(
                        edge)]

            for edge in node.out_edges:
                del edge.source.out_edges[edge.source.out_edges.index(edge)]
                if edge.source.parent_graph == edge.dest.parent_graph:
                    del edge.source.parent_graph.edges[
                        edge.source.parent_graph.edges.index(edge)]
                else:
                    del self.engine.graph.edges[self.engine.graph.edges.index(
                        edge)]
            del graph.nodes[idx]
            self.repaint()

    def removeSubgraph(self, subgraph):
        graph = subgraph.parent_graph
        if (subgraph in graph.subgraphs):
            idx = graph.subgraphs.index(subgraph)
            subgraph = graph.subgraphs[idx]
            if (self.node_removed_callback is not None):
                self.node_removed_callback(subgraph)
            del graph.subgraphs[idx]
            self.repaint()

    def removeEdge(self, edge):
        if (edge in self.engine.graph.edges):
            source = edge.source
            dest = edge.dest
            if (self.edge_removed_callback is not None):
                self.edge_removed_callback(edge)

            idx = source.out_edges.index(edge)
            del source.out_edges[idx]

            idx = dest.in_edges.index(edge)
            del dest.in_edges[idx]

            if edge.source.parent_graph == edge.dest.parent_graph:
                del edge.source.parent_graph.edges[
                    edge.source.parent_graph.edges.index(edge)]
            else:
                del self.engine.graph.edges[self.engine.graph.edges.index(
                    edge)]

            self.repaint()

    def findSubNode(self, graph, x, y):
        for node in graph.nodes:
            gpos = node.global_pos
            if (type(node) == Graph and gpos[0] - node.size[0] / 2 < x
                    and gpos[0] + node.size[0] / 2 > x
                    and gpos[1] - node.size[1] / 2 < y
                    and gpos[1] + node.size[1] / 2 > y):
                return node
        return None

    def findNode(self, graph, x, y):
        for n in graph.nodes:
            gpos = n.global_pos
            if (gpos[0] - n.size[0] / 2 < x and gpos[0] + n.size[0] / 2 > x
                    and gpos[1] - n.size[1] / 2 < y
                    and gpos[1] + n.size[1] / 2 > y):
                return n
        return None

    def findEdge(self, graph, x, y):
        for i, e in enumerate(graph.edges):
            nb_next = 0
            for j in range(i, len(graph.edges)):
                if (graph.edges[j].source == e.source
                        and graph.edges[j].dest == e.dest):
                    nb_next += 1

            offset = [0, 0]
            if (nb_next % 2 == 1):
                offset[0] = 20 * (nb_next / 2)
            else:
                offset[0] = -20 * (nb_next / 2)

            sx = e.source.pos[
                0] if e.source.pos[0] < e.dest.pos[0] else e.dest.pos[0]
            sy = e.source.pos[
                1] if e.source.pos[1] < e.dest.pos[1] else e.dest.pos[1]

            ex = e.source.pos[
                0] if e.source.pos[0] > e.dest.pos[0] else e.dest.pos[0]
            ey = e.source.pos[
                1] if e.source.pos[1] > e.dest.pos[1] else e.dest.pos[1]

            sx += +offset[0]
            ex += +offset[0]

            if (x > sx - self.min_cursor_edge_dist
                    and x < ex + self.min_cursor_edge_dist
                    and y > sy - self.min_cursor_edge_dist
                    and y < ey + self.min_cursor_edge_dist):
                x2 = x - sx
                y2 = y - sy
                dx = (ex - sx)
                dy = (ey - sy)
                if (dx == 0):
                    if (abs(x2) < self.min_cursor_edge_dist):
                        return e
                elif (dy == 0):
                    if (abs(y2) < self.min_cursor_edge_dist):
                        return e
                else:
                    a = -dy / dx
                    if (abs(a * x2 + y2) / math.sqrt(a**2) <
                            self.min_cursor_edge_dist):
                        return e

        return None

    def load_file(self, filename):
        self.engine.graph = self.parser.parseFile(filename)
        self.build()
        self.update()

    def loadAJson(self, filename):
        self.engine.graph = self.parser.fromJSON(filename)
        self.build()
        self.update()

    def save(self, filename):
        self.parser.save(filename, self.engine.graph)

    def saveAsJson(self, filename):
        self.parser.toJSON(filename, self.engine.graph)

    def mouseDoubleClickEvent(self, event):
        x = event.x()
        y = event.y()
        n = self.findNode(self.engine.graph, x, y)
        if n is not None:
            if (self.node_invoked_callback is not None):
                self.node_invoked_callback(n)
        else:
            e = self.findEdge(self.engine.graph, x, y)
            if e is not None:
                if (self.edge_invoked_callback is not None):
                    self.edge_invoked_callback(e)

        QWidget.mouseDoubleClickEvent(self, event)

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            x = event.x()
            y = event.y()
            self.current_pos = [x, y]
            self.mouse_down = True
            n = self.findNode(self.engine.graph, x, y)
            self.selected_Node = n

            if (n is None):
                n = self.findSubNode(self.engine.graph, x, y)
                self.selected_Node = n

        QWidget.mousePressEvent(self, event)

    def mouseMoveEvent(self, event):
        if self.selected_Node is not None and self.mouse_down:
            x = event.x()
            y = event.y()
            if (self.manipulation_mode ==
                    QGraphVizManipulationMode.Nodes_Move_Mode):
                self.selected_Node.pos[0] += x - self.current_pos[0]
                self.selected_Node.pos[1] += y - self.current_pos[1]

            self.current_pos = [x, y]
            self.repaint()
        QWidget.mouseMoveEvent(self, event)

    def mouseReleaseEvent(self, event):
        x = event.x()
        y = event.y()
        n = self.findNode(self.engine.graph, x, y)
        if n is None:
            s = self.findSubNode(self.engine.graph, x, y)
        if n is None:
            e = self.findEdge(self.engine.graph, x, y)
        else:
            e = None
        # Manipulating nodes
        if (self.manipulation_mode == QGraphVizManipulationMode.Nodes_Move_Mode
            ):
            if self.selected_Node is not None and self.mouse_down:
                s = self.findSubNode(self.engine.graph, x, y)
                if (s is not None and s != self.selected_Node):
                    if (type(self.selected_Node) == Node):
                        del self.selected_Node.parent_graph.nodes[
                            self.selected_Node.parent_graph.nodes.index(
                                self.selected_Node)]
                        s.nodes.append(self.selected_Node)
                        self.selected_Node.parent_graph = s
                        self.build()
                        self.repaint()
                    if (type(self.selected_Node) == Graph):
                        del self.selected_Node.parent_graph.nodes[
                            self.selected_Node.parent_graph.nodes.index(
                                self.selected_Node)]
                        s.nodes.append(self.selected_Node)
                        self.selected_Node.parent_graph = s
                        self.build()
                        self.repaint()

        # Connecting edges
        if (self.manipulation_mode ==
                QGraphVizManipulationMode.Edges_Connect_Mode):
            if self.selected_Node is not None and self.mouse_down:
                d = n if n is not None else s
                if (d != self.selected_Node and d is not None):
                    add_the_edge = True
                    if (self.new_edge_beingAdded_callback is not None):
                        add_the_edge, kwargs = self.new_edge_beingAdded_callback(
                            self.selected_Node, d)
                    else:
                        kwargs = {}
                    if add_the_edge:
                        edge = self.addEdge(self.selected_Node, d, kwargs)
                        if (add_the_edge):
                            if (self.new_edge_created_callback is not None):
                                self.new_edge_created_callback(edge)
                        self.build()
                self.selected_Node = None
        # Removing node
        elif (self.manipulation_mode ==
              QGraphVizManipulationMode.Node_remove_Mode):
            if (n is not None):
                self.removeNode(n)
                self.build()
                self.repaint()

        #Removing edge
        elif (self.manipulation_mode ==
              QGraphVizManipulationMode.Edge_remove_Mode):
            if (e is not None):
                self.removeEdge(e)
                self.build()
                self.repaint()
        # Remiving Subgraph
        elif (self.manipulation_mode ==
              QGraphVizManipulationMode.Subgraph_remove_Mode):
            if (s is not None):
                self.removeSubgraph(s)
                self.build()
                self.repaint()

        # Inform application
        if (n is not None):
            if (self.node_selected_callback is not None):
                self.node_selected_callback(n)

        if (e is not None):
            if (self.edge_selected_callback is not None):
                self.edge_selected_callback(e)

        QWidget.mouseReleaseEvent(self, event)
        self.mouse_down = False
        self.repaint()
Example #5
0
class QGraphViz_Core(QWidget):
    """
    Main graphviz widget to draw and interact with graphs
    """
    def __init__(
                    self, 
                    parent=None, 
                    engine=None, 
                    auto_freeze=False,
                    show_subgraphs = True,
                    manipulation_mode=QGraphVizManipulationMode.Nodes_Move_Mode,
                    # Callbacks
                    new_edge_beingAdded_callback=None, # A callback called when a new connection is being added (should return True or False to accept or not the edge, as well as return the edge parameters)
                    new_edge_created_callback=None, # A callbakc called when a new connection is created between two nodes using the GUI
                    node_selected_callback=None, # A callback called when a node is clicked
                    edge_selected_callback=None, # A callback called when an edge is clicked
                    node_invoked_callback=None, # A callback called when a node is double clicked
                    edge_invoked_callback=None, # A callback called when an edge is double clicked
                    node_removed_callback=None, # A callback called when a node is removed
                    edge_removed_callback=None, # A callback called when an edge is removed

                    # Custom options
                    min_cursor_edge_dist=3,
                    hilight_Nodes=False,
                    hilight_Edges=False
                ):
        """
        QGraphViz widget Constructor
        :param parent: A QWidget parent of the QGraphViz widget
        :param engine: The graph processing engine (exemple Dot engine)
        :param show_subgraphs: Tells whether to show the content of subgraphs or not
        :param manipulation_mode: Sets the current graph manipulations mode
        :param new_edge_beingAdded_callback: A callback issued when a new edge is being added. This callback should return a boolean to accept or refuse adding the edge.
        :param new_edge_created_callback: A callback issued when a new edge is added.
        :param node_selected_callback: A callback issued when a node is selected.
        :param edge_selected_callback: A callback issued when an edge is selected.
        :param node_removed_callback: A callback issued when an node is removed.
        :param edge_removed_callback: A callback issued when an edge is removed.
        :param min_cursor_edge_dist: Minimal distance between sursor edge.
        :param hilight_Nodes: If True, whenever mouse is hovered on a node, it is hilighted.
        :param hilight_Edges: If True, whenever mouse is hovered on an edge, it is hilighted.
        """
        
        QWidget.__init__(self,parent)

        self.setSizePolicy(QSizePolicy.Fixed,QSizePolicy.Fixed)
        # Set core stuff
        self.parser = DotParser()
        self.engine=engine

        # Set autofreeze status
        self.auto_freeze = auto_freeze
        
        # Pfrepare lists
        self.qnodes=[]
        self.qedges=[]

        # Nodes manipulation
        self.manipulation_mode = manipulation_mode
        self.selected_Node = None  
        self.hovered_Node = None
        self.hovered_Edge = None
        self.hovered_Edge_id = None
        self.current_pos = [0,0]
        self.mouse_down=False
        self.min_cursor_edge_dist=min_cursor_edge_dist
        self.show_subgraphs=show_subgraphs

        # Set callbacks
        self.new_edge_beingAdded_callback = new_edge_beingAdded_callback
        self.new_edge_created_callback = new_edge_created_callback
        self.node_selected_callback = node_selected_callback
        self.edge_selected_callback = edge_selected_callback
        self.node_invoked_callback = node_invoked_callback
        self.edge_invoked_callback = edge_invoked_callback
        self.node_removed_callback=node_removed_callback
        self.edge_removed_callback=edge_removed_callback

        self.hilight_Nodes=hilight_Nodes
        self.hilight_Edges=hilight_Edges

        self.setAutoFillBackground(True)
        self.setAttribute(Qt.WA_StyledBackground, True)
        self.setMouseTracking(True)

        

    # =================== Exposed methods =======================

    def build(self):
        self.engine.build()
        self.updateSize()

        
    def freeze(self):
        """
        freezes the graph and saves the current nodes positions
        to the node parameters. When loading from JSON, the previous
        position will be reloaded
        """
        for node in self.engine.graph.nodes:
            node.kwargs["pos"]=node.pos
            node.kwargs["size"]=node.size

    def unfreeze(self):
        """
        This removes the effect of the freeze function
        If called, the nodes position can be recomputed in the future
        """
        for node in self.engine.graph.nodes:
            if("pos" in node.kwargs):
                del node.kwargs["pos"]
                del node.kwargs["size"]

    def new(self, engine):
        """
        Creates a new engine
        :param engine: An engine object (for example a Dot engine)
        """
        self.engine=engine

    def addNode(self, graph, node_name, **kwargs):
        """
        Adds a node to a graph or subgraph
        """
        node = Node(node_name, graph, **kwargs)
        graph.nodes.append(node)
        return node

    def addEdge(self, source, dest, kwargs):
        """
        Connects two nodes from the same subgraph or 
        from two different subgraphs
        If source and dest nodes belong to the same
        Subgraph, the connection added to the subgraph
        if the connection is between different subgraph notes
        the connection is added to the main subgraph 
        """
        edge = Edge(source, dest)
        edge.kwargs=kwargs
        if(source.parent_graph == dest.parent_graph):
            source.parent_graph.edges.append(edge)
        else:
            self.engine.graph.edges.append(edge)
        
        return edge

    def addSubgraph(self, parent_graph, subgraph_name, subgraph_type= GraphType.SimpleGraph, **kwargs):
        subgraph = Graph(subgraph_name,subgraph_type, parent_graph, **kwargs)
        subgraph.name = subgraph_name
        subgraph.parent_graph = parent_graph
        parent_graph.nodes.append(subgraph)
        return subgraph

    def removeNode(self, node):
        graph = node.parent_graph
        if(node in graph.nodes):
            idx = graph.nodes.index(node)
            node = graph.nodes[idx]
            if(self.node_removed_callback is not None):
                self.node_removed_callback(node)
            for edge in node.in_edges:
                del edge.source.out_edges[edge.source.out_edges.index(edge)]
                if edge.source.parent_graph == edge.dest.parent_graph:
                    del edge.source.parent_graph.edges[edge.source.parent_graph.edges.index(edge)]
                else:
                    del self.engine.graph.edges[self.engine.graph.edges.index(edge)]

            for edge in node.out_edges:
                del edge.source.out_edges[edge.source.out_edges.index(edge)]
                if edge.source.parent_graph == edge.dest.parent_graph:
                    del edge.source.parent_graph.edges[edge.source.parent_graph.edges.index(edge)]
                else:
                    del self.engine.graph.edges[self.engine.graph.edges.index(edge)]
            del graph.nodes[idx]
            self.repaint()

    def removeSubgraph(self, subgraph):
        graph = subgraph.parent_graph
        if(subgraph in graph.subgraphs):
            idx = graph.subgraphs.index(subgraph)
            subgraph = graph.subgraphs[idx]
            if(self.node_removed_callback is not None):
                self.node_removed_callback(subgraph)
            del graph.subgraphs[idx]
            self.repaint()

    def removeEdge(self, edge):
        if(edge in self.engine.graph.edges):
            source = edge.source
            dest = edge.dest
            if(self.edge_removed_callback is not None):
                self.edge_removed_callback(edge)

            idx = source.out_edges.index(edge)
            del source.out_edges[idx]

            idx = dest.in_edges.index(edge)
            del dest.in_edges[idx]

            if edge.source.parent_graph == edge.dest.parent_graph:
                del edge.source.parent_graph.edges[edge.source.parent_graph.edges.index(edge)]
            else:
                del self.engine.graph.edges[self.engine.graph.edges.index(edge)]

            self.repaint()

    def load_file(self, filename):
        self.engine.graph = self.parser.parseFile(filename)
        self.engine.current_path = filename
        self.build()
        self.update()

    def loadAJson(self, filename):
        self.engine.graph = self.parser.fromJSON(filename)
        self.engine.current_path = filename
        self.build()
        self.update()
        

    def save(self, filename):
        #BUGFIX : unhilight node before saving
        if(self.hovered_Node is not None):
            self.hovered_Node.kwargs["width"] = self.hovered_Node_Back_width
            self.hovered_Node = None

        self.parser.save(filename, self.engine.graph)
        self.engine.current_path=filename

    def saveAsJson(self, filename):
        self.parser.toJSON(filename, self.engine.graph)
        self.engine.current_path=filename


    # ================== Helper methods ==================
    def findSubNode(self, graph, x, y):
        for node in graph.nodes:
            gpos=node.global_pos
            if(
                type(node)==Graph and
                gpos[0]-node.size[0]/2<x and gpos[0]+node.size[0]/2>x and
                gpos[1]-node.size[1]/2<y and gpos[1]+node.size[1]/2>y

            ):
                return node
        return None

    def isNodeHovered(self, n, x, y):
        gpos=n.global_pos
        if(
            gpos[0]-n.size[0]/2<x and gpos[0]+n.size[0]/2>x and
            gpos[1]-n.size[1]/2<y and gpos[1]+n.size[1]/2>y
        ):
            return True
        else:
            return False

    def isEdgeHovered(self, graph, i, e, x, y):
        nb_next=0
        for j in range(i, len(graph.edges)):
            if(graph.edges[j].source==e.source and graph.edges[j].dest==e.dest):
                nb_next+=1

        offset=[0,0]
        if(nb_next%2==1):
            offset[0]=20*(nb_next/2)
        else:
            offset[0]=-20*(nb_next/2)

        sx=e.source.pos[0] if e.source.pos[0]< e.dest.pos[0] else e.dest.pos[0]
        sy=e.source.pos[1] if e.source.pos[1]< e.dest.pos[1] else e.dest.pos[1]

        ex=e.source.pos[0] if e.source.pos[0]> e.dest.pos[0] else e.dest.pos[0]
        ey=e.source.pos[1] if e.source.pos[1]> e.dest.pos[1] else e.dest.pos[1]

        sx += +offset[0]
        ex += +offset[0]

        if(x>sx-self.min_cursor_edge_dist and x<ex+self.min_cursor_edge_dist and
            y>sy-self.min_cursor_edge_dist and y<ey+self.min_cursor_edge_dist):
            x2 = x-sx
            y2 = y-sy 
            dx = (ex-sx)
            dy = (ey-sy)
            if(dx == 0):
                if(abs(x2)<self.min_cursor_edge_dist):
                    return True
            elif(dy == 0):
                if(abs(y2)<self.min_cursor_edge_dist):
                    return True
            else:
                a = -dy/dx
                if(abs(a*x2+y2)/math.sqrt(a**2)<self.min_cursor_edge_dist):
                    return True
        return False
                    
    def findNode(self, graph, x, y):
        """Finds a node by position
        """
        for n in reversed(graph.nodes):
            if(self.isNodeHovered(n, x, y)):
                return n
        return None

    def findEdge(self, graph, x, y):
        """Finds an edge by position
        """
        for i,e in enumerate(reversed(graph.edges)):
            if(self.isEdgeHovered(graph, i, e, x, y)):
                return e,i
        return None,0

    def getRect_Size(self):
        return self.engine.graph.getRect()

    # ================== Mouse events section ===========================

    def mouseDoubleClickEvent(self, event):
        x = event.x()
        y = event.y()
        n = self.findNode(self.engine.graph, x,y)
        if n is not None:
            if(self.node_invoked_callback is not None):
                self.node_invoked_callback(n)
        else:
            e,_ = self.findEdge(self.engine.graph, x, y)
            if e is not None:
                if(self.edge_invoked_callback is not None):
                    self.edge_invoked_callback(e)

        QWidget.mouseDoubleClickEvent(self, event)
        self.leaveEvent()

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            x = event.x()
            y = event.y()
            self.current_pos = [x,y]
            self.mouse_down=True
            n = self.findNode(self.engine.graph, x, y)
            self.selected_Node = n 

            if(n is None):
                n = self.findSubNode(self.engine.graph, x,y)
                self.selected_Node = n                

        QWidget.mousePressEvent(self, event)

    def leaveEvent(self, event=None):
        """
        Used to reset some parameters when the mouse leaves the QWidget
        """
        self.selected_Node=None

        self.mouse_down=False

        if(self.hovered_Node is not None):
            self.hovered_Node.kwargs["width"] = self.hovered_Node_Back_width
            self.hovered_Node = None
        
        if(self.hovered_Edge is not None):
            self.hovered_Edge.kwargs["width"] = self.hovered_Edge_Back_width
            self.hovered_Edge = None

        self.update()
        if(event!=None):
            event.accept()
        

    def mouseMoveEvent(self, event):
        if self.selected_Node is not None and self.mouse_down:
            x = event.x()
            y = event.y()
            if(self.manipulation_mode==QGraphVizManipulationMode.Nodes_Move_Mode):
                self.selected_Node.pos[0] += x-self.current_pos[0]
                self.selected_Node.pos[1] += y-self.current_pos[1]

            self.current_pos = [x,y]
            self.repaint()
        else:
            x = event.x()
            y = event.y()
            if(self.hilight_Nodes):
                if(self.hovered_Node is None):
                    self.hovered_Node = self.findNode(self.engine.graph, x, y)
                    if(self.hovered_Node is not None):
                        if "width" in list(self.hovered_Node.kwargs.keys()):
                            self.hovered_Node_Back_width=self.hovered_Node.kwargs["width"]
                        else:
                            self.hovered_Node_Back_width=1
                        self.hovered_Node.kwargs["width"] = self.hovered_Node_Back_width+3
                        self.update()
                else:
                    if not(self.isNodeHovered(self.hovered_Node, x, y)):
                        self.hovered_Node.kwargs["width"] = self.hovered_Node_Back_width
                        self.hovered_Node = None
                        self.update()
            if(self.hilight_Edges):
                if(self.hovered_Edge is None):
                    self.hovered_Edge, self.hovered_Edge_id = self.findEdge(self.engine.graph, x, y)
                    if(self.hovered_Edge is not None):
                        if "width" in list(self.hovered_Edge.kwargs.keys()):
                            self.hovered_Edge_Back_width=self.hovered_Edge.kwargs["width"]
                        else:
                            self.hovered_Edge_Back_width=1
                        self.hovered_Edge.kwargs["width"] = self.hovered_Edge_Back_width+3
                        self.update()
                else:
                    if not(self.isEdgeHovered(self.engine.graph, self.hovered_Edge_id, self.hovered_Edge, x, y)):
                        self.hovered_Edge.kwargs["width"] = self.hovered_Edge_Back_width
                        self.hovered_Edge = None
                        self.update()

        QWidget.mouseMoveEvent(self, event)

    def mouseReleaseEvent(self, event):
        x = event.x()
        y = event.y()
        n = self.findNode(self.engine.graph, x, y)   
        if n is None:
            s = self.findSubNode(self.engine.graph, x,y)     
        if n is None:
            e, _ = self.findEdge(self.engine.graph, x,y)        
        else:
            e = None
        # Manipulating nodes
        if(self.manipulation_mode==QGraphVizManipulationMode.Nodes_Move_Mode):
            if self.selected_Node is not None and self.mouse_down:
                selected_Node = self.selected_Node
                s = self.findSubNode(self.engine.graph, x,y)
                if(s is not None and s!=selected_Node):
                    if(type(selected_Node)==Node):
                        del selected_Node.parent_graph.nodes[selected_Node.parent_graph.nodes.index(selected_Node)]
                        s.nodes.append(selected_Node)
                        selected_Node.parent_graph = s
                    if(type(selected_Node)==Graph):
                        del selected_Node.parent_graph.nodes[selected_Node.parent_graph.nodes.index(selected_Node)]
                        s.nodes.append(selected_Node)
                        selected_Node.parent_graph = s
                        if(self.auto_freeze):
                            self.freeze()
                if(self.auto_freeze):
                    self.freeze()
                self.build()
                self.repaint()
        # Connecting edges
        if(self.manipulation_mode==QGraphVizManipulationMode.Edges_Connect_Mode):
            if self.selected_Node is not None and self.mouse_down:
                selected_Node = self.selected_Node
                d = n if n is not None else s
                if(d!=selected_Node and d is not None):
                    add_the_edge=True
                    if(self.new_edge_beingAdded_callback is not None):
                        add_the_edge, kwargs=self.new_edge_beingAdded_callback(selected_Node, d)
                    else:
                        kwargs={}
                    if add_the_edge:
                        edge = self.addEdge(selected_Node, d, kwargs)
                        if(add_the_edge):
                            if(self.new_edge_created_callback is not None):
                                self.new_edge_created_callback(edge)
                        self.build()
                self.selected_Node=None
        # Removing node
        elif(self.manipulation_mode==QGraphVizManipulationMode.Node_remove_Mode):
            if(n is not None):
                self.removeNode(n)
                self.build()
                self.repaint()

        #Removing edge
        elif(self.manipulation_mode==QGraphVizManipulationMode.Edge_remove_Mode):
            if(e is not None):
                self.removeEdge(e)
                self.build()
                self.repaint()
        # Remiving Subgraph
        elif(self.manipulation_mode==QGraphVizManipulationMode.Subgraph_remove_Mode):
            if(s is not None):
                self.removeSubgraph(s)
                self.build()
                self.repaint()

        # Inform application
        if(n is not None):
            if(self.node_selected_callback is not None):
                self.node_selected_callback(n)

        if( e is not None):
            if(self.edge_selected_callback is not None):
                self.edge_selected_callback(e)

        self.updateSize()
        QWidget.mouseReleaseEvent(self, event)
        self.mouse_down=False
        self.repaint()

    # ============= Painting section ===============
    def paintSubgraph(self, subgraph, painter, pen, brush):
        if("color" in subgraph.kwargs.keys()):
            pen.setColor(QColor(subgraph.kwargs["color"]))
        else:
            pen.setColor(QColor("black"))

        if("fillcolor" in subgraph.kwargs.keys()):
            if(":" in subgraph.kwargs["fillcolor"]):
                gradient=QLinearGradient(subgraph.pos[0]-subgraph.size[0]/2, subgraph.pos[1], subgraph.pos[0]+subgraph.size[0]/2, subgraph.pos[1])
                c=subgraph.kwargs["fillcolor"].split(":")
                for i, col in enumerate(c):
                    stop = i/(len(c)-1)
                    gradient.setColorAt(stop, QColor(col))

                brush = QBrush(gradient)
            else:
                brush=QBrush(QColor(subgraph.kwargs["fillcolor"]))
        else:
            brush=QBrush(QColor("white"))

        if("width" in subgraph.kwargs.keys()):
            pen.setWidth(int(subgraph.kwargs["width"]))
        else:
            pen.setWidth(1)

        painter.setPen(pen)
        painter.setBrush(brush)
        gpos = subgraph.global_pos

        painter.drawRect(
                    gpos[0]-subgraph.size[0]/2,
                    gpos[1]-subgraph.size[1]/2,
                    subgraph.size[0], subgraph.size[1])

        if("label" in subgraph.kwargs.keys()):
            painter.drawText(
                gpos[0]-subgraph.size[0]/2,
                gpos[1]-subgraph.size[1]/2,
                subgraph.size[0], subgraph.size[1],
                Qt.AlignCenter|Qt.AlignTop,subgraph.kwargs["label"])

    def paintGraph(self, graph, painter):
        brush = QBrush(Qt.SolidPattern)
        pen=QPen()
        brush.setColor(Qt.white)


        for i,edge in enumerate(graph.edges):
            if("color" in edge.kwargs.keys()):
                pen.setColor(QColor(edge.kwargs["color"]))
            else:
                pen.setColor(QColor("black"))

            if("width" in edge.kwargs.keys()):
                pen.setWidth(int(edge.kwargs["width"]))
            else:
                pen.setWidth(1)

            painter.setPen(pen)
            painter.setBrush(brush)
            if(edge.source.parent_graph !=graph and not self.show_subgraphs):
                gspos = edge.source.parent_graph.global_pos
            else:
                gspos = edge.source.global_pos

            if(edge.dest.parent_graph !=graph and not self.show_subgraphs):
                gspos = edge.dest.parent_graph.global_pos
            else:
                gdpos = edge.dest.global_pos

            nb_next=0
            for j in range(i, len(graph.edges)):
                if(graph.edges[j].source==edge.source and graph.edges[j].dest==edge.dest):
                    nb_next+=1

            offset=[0,0]
            if(nb_next%2==1):
                offset[0]=20*(nb_next/2)
            else:
                offset[0]=-20*(nb_next/2)

            path = QPainterPath()
            path.moveTo(gspos[0],gspos[1])
            path.cubicTo(gspos[0],gspos[1],offset[0]+(gspos[0]+gdpos[0])/2,(gspos[1]+gdpos[1])/2,gdpos[0],gdpos[1])
            painter.strokePath(path, pen)
            """
            painter.drawLine(gspos[0],gspos[1],
            gdpos[0],
            gdpos[1])
            """

        if(self.show_subgraphs):
            for node in graph.nodes:
                if type(node)==Graph:
                    subgraph = node
                    self.paintSubgraph(subgraph, painter, pen, brush)
                    
         # TODO : add more painting parameters
        for node in graph.nodes:
            if type(node)!=Graph:
                if("color" in node.kwargs.keys()):
                    pen.setColor(QColor(node.kwargs["color"]))
                else:
                    pen.setColor(QColor("black"))

                if("fillcolor" in node.kwargs.keys()):
                    if(":" in node.kwargs["fillcolor"]):
                        gradient=QLinearGradient(node.pos[0]-node.size[0]/2, node.pos[1], node.pos[0]+node.size[0]/2, node.pos[1])
                        c=node.kwargs["fillcolor"].split(":")
                        for i, col in enumerate(c):
                            stop = i/(len(c)-1)
                            gradient.setColorAt(stop, QColor(col))

                        brush = QBrush(gradient)
                    else:
                        brush=QBrush(QColor(node.kwargs["fillcolor"]))
                else:
                    brush=QBrush(QColor("white"))

                if("width" in node.kwargs.keys()):
                    pen.setWidth(int(node.kwargs["width"]))
                else:
                    pen.setWidth(1)

                gpos = node.global_pos

                painter.setPen(pen)
                painter.setBrush(brush)
                if("shape" in node.kwargs.keys()):
                    if(node.kwargs["shape"]=="box"):
                        painter.drawRect(
                                    gpos[0]-node.size[0]/2,
                                    gpos[1]-node.size[1]/2,
                                    node.size[0], node.size[1])

                    elif(node.kwargs["shape"]=="circle"):
                        painter.drawEllipse(
                                    gpos[0]-node.size[0]/2,
                                    gpos[1]-node.size[1]/2,
                                    node.size[0], node.size[1])
                    elif(node.kwargs["shape"]=="triangle"):
                        rect = QRect(gpos[0]-node.size[0]/2, gpos[1]-2*node.size[1]/3, node.size[0], node.size[1])

                        path = QPainterPath()
                        path.moveTo(rect.left() + (rect.width() / 2), rect.top())
                        path.lineTo(rect.bottomLeft())
                        path.lineTo(rect.bottomRight())
                        path.lineTo(rect.left() + (rect.width() / 2), rect.top())

                        painter.fillPath(path, brush)
                        painter.drawPath(path)
                    elif(node.kwargs["shape"]=="polygon"):
                        rect = QRect(gpos[0]-node.size[0]/2, gpos[1]-node.size[1]/2, node.size[0], node.size[1])

                        path = QPainterPath()
                        path.moveTo(rect.left() + (rect.width() / 4), rect.top())
                        path.lineTo(rect.left() + 3*rect.width()/4, rect.top())
                        path.lineTo(rect.left() + rect.width(), rect.top() + rect.height()/2)
                        path.lineTo(rect.left() + 3*rect.width()/4, rect.top() + rect.height())
                        path.lineTo(rect.left() + rect.width()/4, rect.top() + rect.height())
                        path.lineTo(rect.left(), rect.top() + rect.height()/2)
                        path.lineTo(rect.left() + (rect.width() / 4), rect.top())
 
                        painter.fillPath(path, brush)
                        painter.drawPath(path)
                    elif(node.kwargs["shape"]=="diamond"):
                        rect = QRect(gpos[0]-node.size[0]/2, gpos[1]-node.size[1]/2, node.size[0], node.size[1])

                        path = QPainterPath()
                        path.moveTo(rect.left() + (rect.width() / 2), rect.top())
                        path.lineTo(rect.left() + rect.width(), rect.top() + rect.height()/2)
                        path.lineTo(rect.left() + rect.width()/2, rect.top() + rect.height())
                        path.lineTo(rect.left(), rect.top() + rect.height()/2)
                        path.lineTo(rect.left() + (rect.width() / 2), rect.top())
 
                        painter.fillPath(path, brush)
                        painter.drawPath(path)

                    else: # assuming this is an image
                        # this parameter can be either direct image path
                        # or a relative path (relative to the file path)
                        # It can contain the format path,width,height
                        # or simple path in which case the image file size will be used
                        image = None
                        width = 0
                        height = 0
                        if("," in node.kwargs["shape"]): # if there is a , in the shape, the first part is the path, then width, then height
                            img_params = node.kwargs["shape"].split(",")
                            if len(img_params)==3:# img:width:height
                                img_path = img_params[0]
                                width =  int(img_params[1])
                                height =  int(img_params[2])
                                img_path2 = os.path.join(os.path.dirname(self.engine.current_path),img_path)
                                if(os.path.isfile(img_path)):
                                    image = QImage(img_path)
                                elif(os.path.isfile(img_path2)):
                                    image = QImage(img_path2)
                        else:
                            img_path = node.kwargs["shape"]
                            img_path2 = os.path.join(os.path.dirname(self.engine.current_path),img_path)
                            if(os.path.isfile(img_path)):
                                image = QImage(img_path)
                                width =  image.size().width()
                                height =  image.size().height()
                            elif(os.path.isfile(img_path2)):
                                image = QImage(img_path2)
                                width =  image.size().width()
                                height =  image.size().height()
                                if width==0:
                                    width=100
                                if height==0:
                                    height=100

                        if image is not None:
                            node.size[0] = width if width>node.size[0] else node.size[0]
                            node.size[1] = height if height>node.size[1] else node.size[1]
                            painter.drawImage(
                                QRect(
                                    gpos[0]-node.size[0]/2,
                                    gpos[1]-node.size[1]/2,
                                    node.size[0],
                                    node.size[1]), 
                                image)
                else:
                    painter.drawEllipse(
                                gpos[0]-node.size[0]/2,
                                gpos[1]-node.size[1]/2,
                                node.size[0], node.size[1])


                if("label" in node.kwargs.keys()):
                    txt = node.kwargs["label"].split("\n")
                    width = 0
                    height = 0
                    for t in txt:
                        if(t==""):
                            t="A"
                        rect = self.engine.fm.boundingRect(t)
                        width=rect.width() if rect.width()>width else width
                        height+=rect.height()

                    width+=self.engine.margins[0]
                    height+self.engine.margins[1]
                    painter.drawText(
                        gpos[0]-width/2,
                        gpos[1]-height/2,
                        width, height,
                        Qt.AlignCenter|Qt.AlignTop,node.kwargs["label"])
            else:
                if(self.show_subgraphs):
                    self.paintGraph(subgraph, painter)
                else:
                    subgraph = node
                    self.paintSubgraph(subgraph, painter, pen, brush)

    def paintEvent(self, event):
        painter = QPainter(self) 
        painter.setFont(self.engine.font)
        self.paintGraph(self.engine.graph,painter)
        if( self.manipulation_mode==QGraphVizManipulationMode.Edges_Connect_Mode and 
            self.mouse_down and 
            self.selected_Node is not None):
            bkp = painter.pen()
            pen=QPen(Qt.DashLine)
            painter.setPen(pen)
            painter.drawLine(self.selected_Node.pos[0], self.selected_Node.pos[1],
                             self.current_pos[0],self.current_pos[1])
            painter.setPen(bkp)
        painter.end()

    def updateSize(self):
        x,y,w,h = self.getRect_Size()
        w=x+w
        h=y+h
        self.setMinimumWidth(w)
        self.setMinimumHeight(h)
        if(self.parent is not None):
            if(self.minimumWidth()<self.parent().width()):
                self.setMinimumWidth(self.parent().width())
            if(self.minimumHeight()<self.parent().height()):
                self.setMinimumHeight(self.parent().height())