From df1a77aa4f7a1e31fed55c816b282b0172a588d5 Mon Sep 17 00:00:00 2001
From: Pierre-Antoine Rouby <pierre-antoine.rouby@inrae.fr>
Date: Wed, 22 Mar 2023 11:14:24 +0100
Subject: [PATCH] network: Add some features.

---
 src/model/network/Graph.py      |  14 +++
 src/view/NetworkWindow.py       |   6 +
 src/view/network/GraphWidget.py | 204 +++++++++++++++++++++++---------
 3 files changed, 166 insertions(+), 58 deletions(-)

diff --git a/src/model/network/Graph.py b/src/model/network/Graph.py
index c727c8e9..02a119ea 100644
--- a/src/model/network/Graph.py
+++ b/src/model/network/Graph.py
@@ -71,3 +71,17 @@ class Graph(object):
 
     def remove_edge(self, edge_name:str):
         self._edges = list(filter(lambda e: e.name != edge_name, self._edges))
+
+    def is_upstream_node(self, node):
+        return reduce(
+            lambda acc, e: (acc and (e.node2 != node)),
+            self._edges,
+            True
+        )
+
+    def is_downstream_node(self, node):
+        return reduce(
+            lambda acc, e: (acc and (e.node1 != node)),
+            self._edges,
+            True
+        )
diff --git a/src/view/NetworkWindow.py b/src/view/NetworkWindow.py
index 992ac5eb..a540d03f 100644
--- a/src/view/NetworkWindow.py
+++ b/src/view/NetworkWindow.py
@@ -201,3 +201,9 @@ class NetworkWindow(ASubWindow):
             self.graph_widget.state("del")
         else:
             self.graph_widget.state("move")
+
+    def keyPressEvent(self, event):
+        key = event.key()
+
+        if key == Qt.Key_Escape:
+            self.graph_widget.reset_selection
diff --git a/src/view/network/GraphWidget.py b/src/view/network/GraphWidget.py
index d9cf5303..bd817c65 100644
--- a/src/view/network/GraphWidget.py
+++ b/src/view/network/GraphWidget.py
@@ -90,10 +90,14 @@ class NodeItem(QGraphicsItem):
         painter.setPen(Qt.NoPen)
 
         color = Qt.yellow
-        if self.graph.selectedNode() == self:
-            color = Qt.red
         if self.graph.selectedNewEdgeSrcNode() == self:
             color = Qt.darkRed
+        elif self.graph.selectedItem() == self:
+            color = Qt.red
+        elif self.graph.graph.is_upstream_node(self.node):
+            color = Qt.blue
+        elif self.graph.graph.is_downstream_node(self.node):
+            color = Qt.green
 
         painter.setBrush(QBrush(color))
         painter.drawEllipse(-10, -10, 20, 20)
@@ -108,8 +112,10 @@ class NodeItem(QGraphicsItem):
 
     def mousePressEvent(self, event):
         # Switch selected node
-        if not self.graph.selectedNewEdgeSrcNode():
-            self.graph.setSelectedNode(self)
+        # if not self.graph.selectedItem() is self:
+        #     self.graph.setSelectedItem(self)
+        # elif self.graph.selectedItem() is self:
+        #     self.graph.setSelectedNewEdgeSrcNode(self)
 
         self.update()
         super(NodeItem, self).mousePressEvent(event)
@@ -163,8 +169,8 @@ class EdgeItem(QGraphicsItem):
         self.prepareGeometryChange()
 
     def boundingRect(self):
-        penWidth = 1.0
-        extra = (penWidth + 5) / 2.0
+        pen_width = 3.0
+        extra = (pen_width + 5) / 2.0
 
         return QRectF(
             self.src_pos,
@@ -181,27 +187,64 @@ class EdgeItem(QGraphicsItem):
         if line.length() == 0.0:
             return
 
-        painter.setPen(QPen(Qt.black, 1, Qt.SolidLine, Qt.RoundCap,
-                Qt.RoundJoin))
+        color = Qt.blue
+        if self.graph.selectedItem() == self:
+            color = Qt.red
+
+        painter.setPen(QPen(color, 3, Qt.SolidLine, Qt.RoundCap,
+                            Qt.RoundJoin))
         painter.drawLine(line)
 
+        line_center = QPointF(
+            (self.src_node.pos().x() + self.dest_node.pos().x()) / 2,
+            (self.src_node.pos().y() + self.dest_node.pos().y()) / 2,
+        )
+
         # 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 = (math.pi * 2.0) - angle
+
+        size = 10.0
+        arrow_p1 = line_center + QPointF(
+            math.sin(angle - math.pi / 3) * size,
+            math.cos(angle - math.pi / 3) * size
+        )
+        arrow_p2 = line_center + QPointF(
+            math.sin(angle - math.pi + math.pi / 3) * size,
+            math.cos(angle - math.pi + math.pi / 3) * size
+        )
+
+        painter.drawPolygon(QPolygonF([line_center, arrow_p1, arrow_p2]))
+
+class NewEdgeLine(QGraphicsItem):
+    def __init__(self, src, dest):
+        super(NewEdgeLine, self).__init__()
+
+        self.src = src
+        self.dest = dest
+
+    def boundingRect(self):
+        pen_width = 2.0
+        extra = (pen_width + 5) / 2.0
+
+        return QRectF(
+            self.src,
+            QSizeF(
+                self.dest.x() - self.src.x(),
+                self.dest.y() - self.src.y()
+            )
+        ).normalized().adjusted(-extra, -extra, extra, extra)
+
+    def paint(self, painter, option, widget):
+        line = QLineF(self.src, self.dest)
+        if line.length() == 0.0:
+            return
+
+        color = Qt.darkGray
+        painter.setPen(QPen(color, 2, Qt.SolidLine, Qt.RoundCap,
+                            Qt.RoundJoin))
+        painter.drawLine(line)
 
 class GraphWidget(QGraphicsView):
     changeEdge = pyqtSignal(object)
@@ -214,8 +257,9 @@ class GraphWidget(QGraphicsView):
         self.parent = parent
         self._state = "move"
 
-        self._selectedNode = None
+        self._selectedItem = None
         self._selectedNewEdgeSrcNode = None
+        self.tmp_line = None
 
         self.node_items = []
         self.edge_items = []
@@ -280,7 +324,8 @@ class GraphWidget(QGraphicsView):
 
         self.changeNode.emit(self.sender())
 
-    def del_node(self, node):
+    def del_node(self, item):
+        node = item.node
         edges = list(
             filter(
                 lambda ie: ie.edge.node1 == node or ie.edge.node2 == node,
@@ -302,14 +347,27 @@ class GraphWidget(QGraphicsView):
         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)
+        # iedge = EdgeItem(node1, node2, edge, self)
+        # self.scene().addItem(iedge)
+        # self.edge_items.append(iedge)
         self.setSelectedNewEdgeSrcNode(None)
+
+        self.tmp_line = None
+        self.scene().clear()
+        self.create_items()
+
+        self.changeEdge.emit(self.sender())
+
+    def del_edge(self, item):
+        edge = item.edge
+        self.graph.remove_edge(edge.name)
+
+        self.scene().clear()
+        self.create_items()
+        self.changeNode.emit(self.sender())
         self.changeEdge.emit(self.sender())
 
+
     def update_edges(self, node):
         edges = list(
             filter(
@@ -319,6 +377,8 @@ class GraphWidget(QGraphicsView):
         )
 
         for edge in edges:
+            # edge.node1.update()
+            # edge.node2.update()
             edge.update()
 
     def itemMoved(self):
@@ -328,14 +388,12 @@ class GraphWidget(QGraphicsView):
     def nodeChangePosition(self, pos, node):
         node.node.setPos(pos.x(), pos.y())
 
-    def selectedNode(self):
-        return self._selectedNode
+    def selectedItem(self):
+        return self._selectedItem
 
-    def setSelectedNode(self, node):
-        previous_node = self._selectedNode
-        self._selectedNode = node
-        if node:
-            self.setSelectedNewEdgeSrcNode(None)
+    def setSelectedItem(self, node):
+        previous_node = self._selectedItem
+        self._selectedItem = node
 
         if previous_node:
             previous_node.update()
@@ -346,27 +404,26 @@ class GraphWidget(QGraphicsView):
     def setSelectedNewEdgeSrcNode(self, node):
         previous_node = self._selectedNewEdgeSrcNode
         self._selectedNewEdgeSrcNode = node
-        if node:
-            self.setSelectedNode(None)
 
         if previous_node:
             previous_node.update()
 
+    def reset_selection(self):
+        self.setSelectedNewEdgeSrcNode(None)
+        if self.tmp_line is not None:
+            self.tmp_line = None
+            self.scene().clear()
+            self.create_items()
+
     def keyPressEvent(self, event):
         key = event.key()
 
-        if key == Qt.Key_Up:
-            self.centerNode.moveBy(0, -20)
-        elif key == Qt.Key_Down:
-            self.centerNode.moveBy(0, 20)
-        elif key == Qt.Key_Left:
-            self.centerNode.moveBy(-20, 0)
-        elif key == Qt.Key_Right:
-            self.centerNode.moveBy(20, 0)
-        elif key == Qt.Key_Plus:
+        if key == Qt.Key_Plus:
             self.scaleView(1.2)
         elif key == Qt.Key_Minus:
             self.scaleView(1 / 1.2)
+        elif key == Qt.Key_Escape:
+            self.reset_selection()
         elif key == Qt.Key_Space or key == Qt.Key_Enter:
             for item in self.scene().items():
                 if isinstance(item, Node):
@@ -405,21 +462,28 @@ class GraphWidget(QGraphicsView):
 
         elif self._state == "add":
             items = self.items(event.pos())
-            if not items:
+            nodes = list(filter(lambda i: type(i) == NodeItem, items))
+            if not nodes:
                 self.add_node(pos)
             else:
-                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":
-            items = self.items(event.pos())
+            items = list(
+                filter(
+                    lambda i: type(i) == NodeItem or type(i) == EdgeItem,
+                    self.items(event.pos())
+                )
+            )
             if len(items) > 0:
-                nodes = list(filter(lambda i: type(i) == NodeItem, items))
-                if len(nodes) > 0:
-                    self.del_node(nodes[0].node)
+                item = items[0]
+                if type(item) == NodeItem:
+                    self.del_node(item)
+                elif type(item) == EdgeItem:
+                    self.del_edge(item)
 
         self.update()
         super(GraphWidget, self).mousePressEvent(event)
@@ -429,6 +493,30 @@ class GraphWidget(QGraphicsView):
         super(GraphWidget, self).mouseReleaseEvent(event)
 
     def mouseMoveEvent(self, event):
-        if self._state == "move":
-            self.update()
-            super(GraphWidget, self).mouseMoveEvent(event)
+        pos = self.mapToScene(event.pos())
+        items = self.items(event.pos())
+        selectable_items = list(
+            filter(
+                lambda i: type(i) == NodeItem or type(i) == EdgeItem,
+                items
+            )
+        )
+
+        if selectable_items:
+            self.setSelectedItem(selectable_items[0])
+        else:
+            self.setSelectedItem(None)
+
+        if self.selectedNewEdgeSrcNode() is not None:
+            if self.tmp_line is None:
+                self.tmp_line = NewEdgeLine(
+                    self.selectedNewEdgeSrcNode().pos(),
+                    pos
+                )
+                self.scene().addItem(self.tmp_line)
+            else:
+                self.tmp_line.dest = pos
+            self.tmp_line.update()
+
+        self.update()
+        super(GraphWidget, self).mouseMoveEvent(event)
-- 
GitLab