From 2c0b265a2be6305feaeb7a9b389239154612ad31 Mon Sep 17 00:00:00 2001
From: Pierre-Antoine Rouby <pierre-antoine.rouby@inrae.fr>
Date: Tue, 21 Mar 2023 16:09:47 +0100
Subject: [PATCH] network: Add nodes features.

---
 src/model/network/Graph.py      |   8 +-
 src/model/network/Node.py       |   4 +
 src/view/NetworkWindow.py       |  33 +++++---
 src/view/network/GraphWidget.py | 146 +++++++++++++++++++++++++++-----
 4 files changed, 153 insertions(+), 38 deletions(-)

diff --git a/src/model/network/Graph.py b/src/model/network/Graph.py
index 78ea877c..c727c8e9 100644
--- a/src/model/network/Graph.py
+++ b/src/model/network/Graph.py
@@ -19,13 +19,13 @@ class Graph(object):
         return f"Graph {{nodes: {self._nodes}, edges: {self._edges}}}"
 
     def nodes(self):
-        return self._nodes.copy()
+        return self._nodes
 
     def nodes_names(self):
         return list(map(lambda n: n.name, self._nodes))
 
     def edges(self):
-        return self._edges.copy()
+        return self._edges
 
     def nodes_counts(self):
         return len(self._nodes)
@@ -67,7 +67,7 @@ class Graph(object):
         return edge
 
     def remove_node(self, node_name:str):
-        self._nodes = filter(lambda n: n.name != node_name, self._nodes)
+        self._nodes = list(filter(lambda n: n.name != node_name, self._nodes))
 
     def remove_edge(self, edge_name:str):
-        self._edges = filter(lambda e: e.name != edge_name, self._edges)
+        self._edges = list(filter(lambda e: e.name != edge_name, self._edges))
diff --git a/src/model/network/Node.py b/src/model/network/Node.py
index 46ed203f..73e1a7b0 100644
--- a/src/model/network/Node.py
+++ b/src/model/network/Node.py
@@ -31,3 +31,7 @@ class Node(object):
             self.name = value
         elif name == "id":
             self.id = value
+
+    def setPos(self, x, y):
+        self.pos.x = x
+        self.pos.y = y
diff --git a/src/view/NetworkWindow.py b/src/view/NetworkWindow.py
index a9065028..5f2ab32a 100644
--- a/src/view/NetworkWindow.py
+++ b/src/view/NetworkWindow.py
@@ -63,11 +63,16 @@ class ComboBoxDelegate(QItemDelegate):
 
 
 class TableModel(QAbstractTableModel):
-    def __init__(self, headers=[], rows=[], graph=None):
+    def __init__(self, headers=[], graph=None, rows_type="nodes"):
         super(QAbstractTableModel, self).__init__()
-        self.rows = rows
         self.headers = headers
         self.graph = graph
+        self._type = rows_type
+
+        if self._type == "nodes":
+            self.rows = graph.nodes()
+        elif self._type == "edges":
+            self.rows = graph.edges()
 
     def flags(self, index):
         return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable
@@ -113,9 +118,12 @@ class TableModel(QAbstractTableModel):
             return False
 
     def update(self):
-        print("update")
-        self.layoutChanged.emit()
+        if self._type == "nodes":
+            self.rows = self.graph.nodes()
+        elif self._type == "edges":
+            self.rows = self.graph.edges()
 
+        self.layoutChanged.emit()
 
 class NetworkWindow(ASubWindow):
     def __init__(self, title="Network", parent=None):
@@ -123,9 +131,6 @@ class NetworkWindow(ASubWindow):
         self.ui.setWindowTitle(title)
 
         self.graph = Graph()
-        n1 = self.graph.add_node()
-        n2 = self.graph.add_node(50.0,50.0)
-        e1 = self.graph.add_edge(n1,n2)
 
         # Graph Widget
 
@@ -143,8 +148,8 @@ class NetworkWindow(ASubWindow):
 
         self.nodes_model = TableModel(
             headers = ["name", "id", "pos"],
-            rows = self.graph.nodes(),
             graph = self.graph,
+            rows_type="nodes",
         )
         self.delegate_line = LineEditDelegate(parent=self)
         table = self.find(QTableView, "tableView_nodes")
@@ -156,8 +161,8 @@ class NetworkWindow(ASubWindow):
 
         self.reachs_model = TableModel(
             headers = ["name", "node1", "node2"],
-            rows = self.graph.edges(),
             graph = self.graph,
+            rows_type="edges"
         )
         self.delegate_combobox = ComboBoxDelegate(
             graph = self.graph,
@@ -173,20 +178,26 @@ class NetworkWindow(ASubWindow):
 
         self.nodes_model.dataChanged.connect(self.reachs_model.update)
         self.reachs_model.dataChanged.connect(self.nodes_model.update)
+        self.graph_widget.changeEdge.connect(self.reachs_model.update)
+        self.graph_widget.changeNode.connect(self.nodes_model.update)
 
         self.find(QPushButton, "pushButton_add").clicked.connect(
             self.clicked_add
         )
-
         self.find(QPushButton, "pushButton_del").clicked.connect(
             self.clicked_del
         )
 
-
     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"
+        else:
+            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"
+        else:
+            self.graph_widget.state = "move"
diff --git a/src/view/network/GraphWidget.py b/src/view/network/GraphWidget.py
index a7684982..6bdb759d 100644
--- a/src/view/network/GraphWidget.py
+++ b/src/view/network/GraphWidget.py
@@ -8,7 +8,7 @@ from model.network.Graph import Graph
 
 from PyQt5.QtCore import (
     qAbs, QLineF, QPointF, QPoint, qrand, QRectF, QSizeF, qsrand,
-    Qt, QTime
+    Qt, QTime, pyqtSlot, pyqtSignal,
 )
 from PyQt5.QtGui import (
     QBrush, QColor, QLinearGradient, QPainter,
@@ -20,6 +20,36 @@ from PyQt5.QtWidgets import (
     QGraphicsView, QStyle
 )
 
+class LandMark(QGraphicsItem):
+    def __init__(self, width, height, parent=None):
+        super(LandMark, self).__init__()
+
+        self.width = width
+        self.height = height
+
+        self.center = QPoint(width / 2, height / 2)
+        self.parent = parent
+
+    def paint(self, painter, option, widget):
+        painter.setPen(Qt.NoPen)
+        painter.setPen(QPen(Qt.black, 0))
+
+        painter.drawLine(
+            -10 + self.center.x(), 0 + self.center.y(),
+            10 + self.center.x(), 0 + self.center.y()
+        )
+        painter.drawLine(
+            0 + self.center.x(), -10 + self.center.y(),
+            0 + self.center.x(), 10 + self.center.y()
+        )
+
+    def boundingRect(self):
+        return QRectF(
+            -10 + self.center.x(), -10 + self.center.y(),
+            10 + self.center.x(), 10 + self.center.y()
+        )
+
+
 class NodeItem(QGraphicsItem):
     Type = QGraphicsItem.UserType + 1
 
@@ -58,33 +88,39 @@ class NodeItem(QGraphicsItem):
 
     def paint(self, painter, option, widget):
         painter.setPen(Qt.NoPen)
-        # painter.setBrush(Qt.darkGray)
-        # painter.drawEllipse(-7, -7, 20, 20)
-
-        gradient = QRadialGradient(-3, -3, 10)
-        if option.state & QStyle.State_Sunken:
-            gradient.setCenter(3, 3)
-            gradient.setFocalPoint(3, 3)
-            gradient.setColorAt(1, QColor(Qt.yellow).lighter(120))
-            gradient.setColorAt(0, QColor(Qt.darkYellow).lighter(120))
-        else:
-            gradient.setColorAt(0, Qt.yellow)
-            gradient.setColorAt(1, Qt.darkYellow)
 
-        painter.setBrush(QBrush(gradient))
-        # painter.setPen(QPen(Qt.black, 0))
+        color = Qt.yellow
+        if self.graph.selectedNode() == self:
+            color = Qt.red
+
+        painter.setBrush(QBrush(color))
         painter.drawEllipse(-10, -10, 20, 20)
 
     def itemChange(self, change, value):
         if change == QGraphicsItem.ItemPositionHasChanged:
             self.graph.itemMoved()
+            self.graph.nodeChangePosition(value, 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()
+
         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)
@@ -191,17 +227,22 @@ class EdgeItem(QGraphicsItem):
         painter.drawPolygon(QPolygonF([line.p1(), sourceArrowP1, sourceArrowP2]))
         painter.drawPolygon(QPolygonF([line.p2(), destArrowP1, destArrowP2]))
 
-
 class GraphWidget(QGraphicsView):
+    changeEdge = pyqtSignal(object)
+    changeNode = pyqtSignal(object)
+
     def __init__(self, graph, parent=None):
         super(GraphWidget, self).__init__(parent=parent)
 
         self.timerId = 0
         self.parent = parent
+        self.state = "move"
+
+        self._selectedNode = None
 
         scene = QGraphicsScene(self)
         scene.setItemIndexMethod(QGraphicsScene.NoIndex)
-        scene.setSceneRect(0, 0, 10000, 10000)
+        scene.setSceneRect(0, 0, 1000, 1000)
         self.setScene(scene)
         self.setCacheMode(QGraphicsView.CacheBackground)
         self.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate)
@@ -209,23 +250,49 @@ class GraphWidget(QGraphicsView):
         self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
         self.setResizeAnchor(QGraphicsView.AnchorViewCenter)
 
-        # scene.addItem(LandMark())
-
         self.graph = graph
+
+        self.scale(1, 1)
+        self.previousScale = 1
+        self.setMinimumSize(400, 400)
+
+        self.create_items()
+
+    def create_items(self):
         for node in self.graph.nodes():
-            scene.addItem(NodeItem(node, self))
+            self.scene().addItem(NodeItem(node, self))
 
         # for edge in self.graph.edges():
         #     scene.addItem(EdgeItem(edge))
 
-        self.scale(1, 1)
-        self.previousScale = 1
-        self.setMinimumSize(400, 400)
+        # self.mark = LandMark(1000, 1000, self)
+        # self.scene().addItem(self.mark)
+
+    def add_node(self, pos):
+        node = self.graph.add_node(pos.x(), pos.y())
+        self.scene().addItem(NodeItem(node, self))
+        self.changeNode.emit(self.sender())
+
+    def del_node(self, node):
+        self.scene().clear()
+        self.graph.remove_node(node.name)
+
+        self.create_items()
+        self.changeNode.emit(self.sender())
 
     def itemMoved(self):
         if not self.timerId:
             self.timerId = self.startTimer(1000 / 25)
 
+    def nodeChangePosition(self, pos, node):
+        node.node.setPos(pos.x(), pos.y())
+
+    def selectedNode(self):
+        return self._selectedNode
+
+    def setSelectedNode(self, node):
+        self._selectedNode = node
+
     def keyPressEvent(self, event):
         key = event.key()
 
@@ -270,3 +337,36 @@ class GraphWidget(QGraphicsView):
         # print(f"{scaleFactor} : {factor}")
 
         self.scale(scaleFactor, scaleFactor)
+
+    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
+
+        elif self.state == "add":
+            items = self.items(event.pos())
+            if not items:
+                self.add_node(pos)
+            else:
+                print(f"TODO add edge {items}")
+
+        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:
+                    self.del_node(nodes[0].node)
+
+        self.update()
+        super(GraphWidget, self).mousePressEvent(event)
+
+    def mouseReleaseEvent(self, event):
+        self.update()
+        super(GraphWidget, self).mouseReleaseEvent(event)
+
+    def mouseMoveEvent(self, event):
+        if self.state == "move":
+            self.update()
+            super(GraphWidget, self).mouseMoveEvent(event)
-- 
GitLab