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