From 9e892ba587d9e45a94114a7da5d93147368a102a Mon Sep 17 00:00:00 2001
From: Pierre-Antoine Rouby <pierre-antoine.rouby@inrae.fr>
Date: Tue, 21 Mar 2023 17:29:42 +0100
Subject: [PATCH] network: Add edge feature and delete node feature.

---
 src/view/NetworkWindow.py       |   8 +-
 src/view/network/GraphWidget.py | 222 ++++++++++++++++++++------------
 2 files changed, 146 insertions(+), 84 deletions(-)

diff --git a/src/view/NetworkWindow.py b/src/view/NetworkWindow.py
index 5f2ab32a..992ac5eb 100644
--- a/src/view/NetworkWindow.py
+++ b/src/view/NetworkWindow.py
@@ -191,13 +191,13 @@ class NetworkWindow(ASubWindow):
     def clicked_add(self):
         if self.get_push_button_checkable("pushButton_add"):
             self.set_push_button_checkable("pushButton_del", False)
-            self.graph_widget.state = "add"
+            self.graph_widget.state("add")
         else:
-            self.graph_widget.state = "move"
+            self.graph_widget.state("move")
 
     def clicked_del(self):
         if self.get_push_button_checkable("pushButton_del"):
             self.set_push_button_checkable("pushButton_add", False)
-            self.graph_widget.state = "del"
+            self.graph_widget.state("del")
         else:
-            self.graph_widget.state = "move"
+            self.graph_widget.state("move")
diff --git a/src/view/network/GraphWidget.py b/src/view/network/GraphWidget.py
index 6bdb759d..d9cf5303 100644
--- a/src/view/network/GraphWidget.py
+++ b/src/view/network/GraphWidget.py
@@ -92,6 +92,8 @@ class NodeItem(QGraphicsItem):
         color = Qt.yellow
         if self.graph.selectedNode() == self:
             color = Qt.red
+        if self.graph.selectedNewEdgeSrcNode() == self:
+            color = Qt.darkRed
 
         painter.setBrush(QBrush(color))
         painter.drawEllipse(-10, -10, 20, 20)
@@ -101,26 +103,17 @@ class NodeItem(QGraphicsItem):
             self.graph.itemMoved()
             self.graph.nodeChangePosition(value, self)
 
+        self.graph.update_edges(self)
         return super(NodeItem, self).itemChange(change, value)
 
     def mousePressEvent(self, event):
         # Switch selected node
-        previous_node = self.graph.selectedNode()
-        self.graph.setSelectedNode(self)
-
-        # Update previous node
-        if previous_node:
-            previous_node.update()
+        if not self.graph.selectedNewEdgeSrcNode():
+            self.graph.setSelectedNode(self)
 
         self.update()
         super(NodeItem, self).mousePressEvent(event)
 
-    # def mouseMoveEvent(self, event):
-    #     self.node.setPos(event.pos().x(), event.pos().y())
-
-    #     self.update()
-    #     super(NodeItem, self).mousePressEvent(event)
-
     def mouseReleaseEvent(self, event):
         self.update()
         super(NodeItem, self).mouseReleaseEvent(event)
@@ -128,79 +121,62 @@ class NodeItem(QGraphicsItem):
 class EdgeItem(QGraphicsItem):
     Type = QGraphicsItem.UserType + 2
 
-    def __init__(self, edge):
+    def __init__(self, src_node_item, dest_node_item, edge, graph_widget):
         super(EdgeItem, self).__init__()
 
-        self.arrowSize = 10.0
-        self.sourcePoint = QPointF()
-        self.destPoint = QPointF()
+        self.src_node = src_node_item
+        self.dest_node = dest_node_item
+        self.edge = edge
+
+        self.graph = graph_widget
+
+        self.src_pos = src_node_item.pos()
+        self.dest_pos = dest_node_item.pos()
 
         self.setAcceptedMouseButtons(Qt.NoButton)
-        self.source = edge.node1
-        self.dest = edge.node2
-        self.edge = edge
         self.adjust()
 
     def type(self):
         return Edge.Type
 
     def sourceNode(self):
-        return self.source
+        return self.src_node.node
 
-    def setSourceNode(self, node):
-        self.source = node
+    def setSourceNode(self, node_item):
+        self.src_node = node_item
         self.adjust()
 
     def destNode(self):
         return self.dest
 
-    def setDestNode(self, node):
-        self.dest = node
+    def setDestNode(self, node_item):
+        self.dest_node = node_item
         self.adjust()
 
     def adjust(self):
-        if not self.source or not self.dest:
-            return
-
         line = QLineF(
-            self.mapFromItem(self.source, 0, 0),
-            self.mapFromItem(self.dest, 0, 0)
+            self.mapFromItem(self.src_node, 0, 0),
+            self.mapFromItem(self.dest_node, 0, 0)
         )
         length = line.length()
 
         self.prepareGeometryChange()
 
-        if length > 20.0:
-            edgeOffset = QPointF((line.dx() * 10) / length,
-                    (line.dy() * 10) / length)
-
-            self.sourcePoint = line.p1() + edgeOffset
-            self.destPoint = line.p2() - edgeOffset
-        else:
-            self.sourcePoint = line.p1()
-            self.destPoint = line.p1()
-
     def boundingRect(self):
-        if not self.source or not self.dest:
-            return QRectF()
-
         penWidth = 1.0
-        extra = (penWidth + self.arrowSize) / 2.0
+        extra = (penWidth + 5) / 2.0
 
         return QRectF(
-            self.sourcePoint,
+            self.src_pos,
             QSizeF(
-                self.destPoint.x() - self.sourcePoint.x(),
-                self.destPoint.y() - self.sourcePoint.y()
+                self.dest_pos.x() - self.src_pos.x(),
+                self.dest_pos.y() - self.src_pos.y()
             )
         ).normalized().adjusted(-extra, -extra, extra, extra)
 
     def paint(self, painter, option, widget):
-        if not self.source or not self.dest:
-            return
-
         # Draw the line itself.
-        line = QLineF(self.sourcePoint, self.destPoint)
+        line = QLineF(self.src_node.pos(), self.dest_node.pos())
 
         if line.length() == 0.0:
             return
@@ -210,22 +186,22 @@ class EdgeItem(QGraphicsItem):
         painter.drawLine(line)
 
         # Draw the arrows if there's enough room.
-        angle = math.acos(line.dx() / line.length())
-        if line.dy() >= 0:
-            angle = Edge.TwoPi - angle
-
-        sourceArrowP1 = self.sourcePoint + QPointF(math.sin(angle + Edge.Pi / 3) * self.arrowSize,
-                                                   math.cos(angle + Edge.Pi / 3) * self.arrowSize)
-        sourceArrowP2 = self.sourcePoint + QPointF(math.sin(angle + Edge.Pi - Edge.Pi / 3) * self.arrowSize,
-                                                   math.cos(angle + Edge.Pi - Edge.Pi / 3) * self.arrowSize);
-        destArrowP1 = self.destPoint + QPointF(math.sin(angle - Edge.Pi / 3) * self.arrowSize,
-                                               math.cos(angle - Edge.Pi / 3) * self.arrowSize)
-        destArrowP2 = self.destPoint + QPointF(math.sin(angle - Edge.Pi + Edge.Pi / 3) * self.arrowSize,
-                                               math.cos(angle - Edge.Pi + Edge.Pi / 3) * self.arrowSize)
-
-        painter.setBrush(Qt.black)
-        painter.drawPolygon(QPolygonF([line.p1(), sourceArrowP1, sourceArrowP2]))
-        painter.drawPolygon(QPolygonF([line.p2(), destArrowP1, destArrowP2]))
+        # angle = math.acos(line.dx() / line.length())
+        # if line.dy() >= 0:
+        #     angle = Edge.TwoPi - angle
+
+        # sourceArrowP1 = self.sourcePoint + QPointF(math.sin(angle + Edge.Pi / 3) * self.arrowSize,
+        #                                            math.cos(angle + Edge.Pi / 3) * self.arrowSize)
+        # sourceArrowP2 = self.sourcePoint + QPointF(math.sin(angle + Edge.Pi - Edge.Pi / 3) * self.arrowSize,
+        #                                            math.cos(angle + Edge.Pi - Edge.Pi / 3) * self.arrowSize);
+        # destArrowP1 = self.destPoint + QPointF(math.sin(angle - Edge.Pi / 3) * self.arrowSize,
+        #                                        math.cos(angle - Edge.Pi / 3) * self.arrowSize)
+        # destArrowP2 = self.destPoint + QPointF(math.sin(angle - Edge.Pi + Edge.Pi / 3) * self.arrowSize,
+        #                                        math.cos(angle - Edge.Pi + Edge.Pi / 3) * self.arrowSize)
+
+        # painter.setBrush(Qt.black)
+        # painter.drawPolygon(QPolygonF([line.p1(), sourceArrowP1, sourceArrowP2]))
+        # painter.drawPolygon(QPolygonF([line.p2(), destArrowP1, destArrowP2]))
 
 class GraphWidget(QGraphicsView):
     changeEdge = pyqtSignal(object)
@@ -236,9 +212,13 @@ class GraphWidget(QGraphicsView):
 
         self.timerId = 0
         self.parent = parent
-        self.state = "move"
+        self._state = "move"
 
         self._selectedNode = None
+        self._selectedNewEdgeSrcNode = None
+
+        self.node_items = []
+        self.edge_items = []
 
         scene = QGraphicsScene(self)
         scene.setItemIndexMethod(QGraphicsScene.NoIndex)
@@ -259,26 +239,87 @@ class GraphWidget(QGraphicsView):
         self.create_items()
 
     def create_items(self):
+        self.node_items = []
+        self.edge_items = []
+
         for node in self.graph.nodes():
-            self.scene().addItem(NodeItem(node, self))
+            inode = NodeItem(node, self)
+            self.scene().addItem(inode)
+            self.node_items.append(inode)
+
+        for edge in self.graph.edges():
+            n1 = list(
+                filter(
+                    lambda n: n.node.name == edge.node1.name,
+                    self.node_items
+                )
+            )
+
+            n2 = list(
+                filter(
+                    lambda n: n.node.name == edge.node2.name,
+                    self.node_items
+                )
+            )
 
-        # for edge in self.graph.edges():
-        #     scene.addItem(EdgeItem(edge))
+            iedge = EdgeItem(n1[0], n2[0], edge, self)
+            self.scene().addItem(iedge)
+            self.edge_items.append(iedge)
 
         # self.mark = LandMark(1000, 1000, self)
         # self.scene().addItem(self.mark)
 
+    def state(self, status):
+        self._state = status
+
     def add_node(self, pos):
         node = self.graph.add_node(pos.x(), pos.y())
-        self.scene().addItem(NodeItem(node, self))
+        inode = NodeItem(node, self)
+        self.scene().addItem(inode)
+        self.node_items.append(inode)
+
         self.changeNode.emit(self.sender())
 
     def del_node(self, node):
-        self.scene().clear()
+        edges = list(
+            filter(
+                lambda ie: ie.edge.node1 == node or ie.edge.node2 == node,
+                self.edge_items
+            )
+        )
+
+        for edge in edges:
+            self.graph.remove_edge(edge.edge.name)
+
         self.graph.remove_node(node.name)
 
+        self.scene().clear()
         self.create_items()
         self.changeNode.emit(self.sender())
+        self.changeEdge.emit(self.sender())
+
+    def add_edge(self, node1, node2):
+        if node1 == node2:
+            return
+        edge = self.graph.add_edge(node1.node, node2.node)
+        iedge = EdgeItem(node1, node2, edge, self)
+        self.scene().addItem(iedge)
+        self.edge_items.append(iedge)
+
+        self.setSelectedNode(None)
+        self.setSelectedNewEdgeSrcNode(None)
+        self.changeEdge.emit(self.sender())
+
+    def update_edges(self, node):
+        edges = list(
+            filter(
+                lambda ie: ie.edge.node1 == node.node or ie.edge.node2 == node.node,
+                self.edge_items
+            )
+        )
+
+        for edge in edges:
+            edge.update()
 
     def itemMoved(self):
         if not self.timerId:
@@ -291,7 +332,25 @@ class GraphWidget(QGraphicsView):
         return self._selectedNode
 
     def setSelectedNode(self, node):
+        previous_node = self._selectedNode
         self._selectedNode = node
+        if node:
+            self.setSelectedNewEdgeSrcNode(None)
+
+        if previous_node:
+            previous_node.update()
+
+    def selectedNewEdgeSrcNode(self):
+        return self._selectedNewEdgeSrcNode
+
+    def setSelectedNewEdgeSrcNode(self, node):
+        previous_node = self._selectedNewEdgeSrcNode
+        self._selectedNewEdgeSrcNode = node
+        if node:
+            self.setSelectedNode(None)
+
+        if previous_node:
+            previous_node.update()
 
     def keyPressEvent(self, event):
         key = event.key()
@@ -340,20 +399,23 @@ class GraphWidget(QGraphicsView):
 
     def mousePressEvent(self, event):
         pos = self.mapToScene(event.pos())
-        if self.state == "move":
-            self.mouse_origin_x = pos.x
-            self.mouse_origin_y = pos.y
+        if self._state == "move":
+            self.mouse_origin_x = pos.x()
+            self.mouse_origin_y = pos.y()
 
-        elif self.state == "add":
+        elif self._state == "add":
             items = self.items(event.pos())
             if not items:
                 self.add_node(pos)
             else:
-                print(f"TODO add edge {items}")
+                nodes = list(filter(lambda i: type(i) == NodeItem, items))
+                if self.selectedNewEdgeSrcNode() is None:
+                    self.setSelectedNewEdgeSrcNode(nodes[0])
+                else:
+                    self.add_edge(self.selectedNewEdgeSrcNode(), nodes[0])
 
-        elif self.state == "del":
+        elif self._state == "del":
             items = self.items(event.pos())
-            print(f"del: {items}")
             if len(items) > 0:
                 nodes = list(filter(lambda i: type(i) == NodeItem, items))
                 if len(nodes) > 0:
@@ -367,6 +429,6 @@ class GraphWidget(QGraphicsView):
         super(GraphWidget, self).mouseReleaseEvent(event)
 
     def mouseMoveEvent(self, event):
-        if self.state == "move":
+        if self._state == "move":
             self.update()
             super(GraphWidget, self).mouseMoveEvent(event)
-- 
GitLab