This commit is contained in:
2026-07-20 14:02:22 +02:00
parent 8f87a3b477
commit bf2512995f
9 changed files with 295 additions and 42 deletions

View File

@@ -23,6 +23,7 @@ from PySide6.QtWidgets import (
QGraphicsScene,
QGraphicsSceneContextMenuEvent,
QGraphicsSceneMouseEvent,
QGraphicsSimpleTextItem,
QGraphicsView,
QApplication,
QMenu,
@@ -78,6 +79,48 @@ class ConnectionPortItem(QGraphicsEllipseItem):
self.setToolTip(label)
class NameLabelItem(QGraphicsSimpleTextItem):
"""Movable italic name label whose position is stored by its owner."""
def __init__(
self,
text: str,
owner_kind: str,
owner_id: str,
controller: DocumentController,
parent: QGraphicsItem | None = None,
) -> None:
super().__init__(text, parent)
self.owner_kind = owner_kind
self.owner_id = owner_id
self.controller = controller
font = self.font()
font.setItalic(True)
self.setFont(font)
self.setBrush(QColor("#303030"))
self.setCursor(Qt.CursorShape.SizeAllCursor)
self.drag_offset = QPointF()
def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
parent = self.parentItem()
point = parent.mapFromScene(event.scenePos()) if parent else event.scenePos()
self.drag_offset = self.pos() - point
event.accept()
def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
parent = self.parentItem()
point = parent.mapFromScene(event.scenePos()) if parent else event.scenePos()
self.setPos(_snapped(point + self.drag_offset))
event.accept()
def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
if self.owner_kind == "component":
self.controller.move_component_name_label(self.owner_id, self.pos())
else:
self.controller.move_connection_name_label(self.owner_id, self.pos())
event.accept()
class ComponentGraphicsItem(QGraphicsObject):
WIDTH = 128.0
HEIGHT = 128.0
@@ -98,6 +141,31 @@ class ComponentGraphicsItem(QGraphicsObject):
self.output_ports = self._create_ports(component.outputs, "source", self.WIDTH)
self.setTransformOriginPoint(self.hitbox.center())
self.setRotation(component.rotation)
self.name_label: NameLabelItem | None = None
self.sync_name_label(component)
def sync_name_label(self, component: Component) -> None:
if component.properties.get("showName", False):
if self.name_label is None:
self.name_label = NameLabelItem(
component.name, "component", component.id, self.controller, self
)
self.name_label.setText(component.name)
position = component.properties.get("nameLabelPosition")
if isinstance(position, dict):
self.name_label.setPos(float(position["x"]), float(position["y"]))
else:
bounds = self.name_label.boundingRect()
self.name_label.setPos(
self.hitbox.center().x() - bounds.width() / 2,
self.hitbox.bottom() + 6,
)
self.name_label.setRotation(-component.rotation)
elif self.name_label is not None:
if self.name_label.scene() is not None:
self.name_label.setParentItem(None)
self.name_label.scene().removeItem(self.name_label)
self.name_label = None
def _create_ports(self, ports, role: str, x: float) -> dict[str, ConnectionPortItem]:
result = {}
@@ -255,13 +323,14 @@ class InterfaceTerminalItem(QGraphicsObject):
class ConnectionGraphicsItem(QGraphicsPathItem):
def __init__(
self,
connection_id: str,
name: str = "",
connection: Connection,
controller: DocumentController,
style: ConnectionStyle | None = None,
) -> None:
super().__init__()
self.connection_id = connection_id
self.name = name
self.connection_id = connection.id
self.name = connection.name
self.controller = controller
self.style = style or ConnectionStyle()
self.start = QPointF()
self.end = QPointF()
@@ -270,8 +339,34 @@ class ConnectionGraphicsItem(QGraphicsPathItem):
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable)
self._update_pen()
self.setZValue(0)
self.setToolTip(name or "Connection")
self.setToolTip(connection.name or "Connection")
self.handles: list[WaypointHandle] = []
self.name_label: NameLabelItem | None = None
self.sync_name_label(connection)
def sync_name_label(self, connection: Connection) -> None:
visible = bool(connection.properties.get("showName", False))
if not visible:
if self.name_label is not None and self.name_label.scene() is not None:
self.name_label.setParentItem(None)
self.name_label.scene().removeItem(self.name_label)
self.name_label = None
return
if self.name_label is None:
self.name_label = NameLabelItem(
connection.name, "connection", connection.id, self.controller, self
)
self.name_label.setText(connection.name)
position = connection.properties.get("nameLabelPosition")
if isinstance(position, dict):
self.name_label.setPos(float(position["x"]), float(position["y"]))
elif not self.path().isEmpty():
bounds = self.path().boundingRect()
label_bounds = self.name_label.boundingRect()
self.name_label.setPos(
bounds.center().x() - label_bounds.width() / 2,
bounds.bottom() + 6,
)
def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: # noqa: N802
if not self.isSelected():
@@ -318,6 +413,10 @@ class ConnectionGraphicsItem(QGraphicsPathItem):
if len(points) > 1:
self.start_direction = points[1] - points[0]
self.end_direction = points[-1] - points[-2]
if self.name_label is not None:
connection = self.controller.active_graph.connections.get(self.connection_id)
if connection is not None:
self.sync_name_label(connection)
def set_waypoints(self, points: list[QPointF]) -> None:
for handle in self.handles:
@@ -713,6 +812,7 @@ class GraphScene(QGraphicsScene):
controller.activeGraphChanged.connect(self.rebuild)
controller.componentMoved.connect(self.set_component_position)
controller.componentRotated.connect(self.set_component_rotation)
controller.componentPropertiesChanged.connect(self.refresh_component_properties)
controller.graphItemChanged.connect(self.refresh_graph_item)
self.rebuild()
@@ -746,8 +846,8 @@ class GraphScene(QGraphicsScene):
self.component_items[component.id] = item
for connection in owner.graph.connections.values():
item = ConnectionGraphicsItem(
connection.id,
connection.name,
connection,
self.controller,
connection_style(self.controller.connection_port_type(connection)),
)
self.addItem(item)
@@ -774,10 +874,22 @@ class GraphScene(QGraphicsScene):
item = self.component_items.get(component_id)
if item is not None:
item.setRotation(rotation)
if item.name_label is not None:
item.name_label.setRotation(-rotation)
self.update_connections_for_block(component_id)
def refresh_component_properties(self, component_id: str) -> None:
component = (
self.controller.document.find_component(component_id)
if self.controller.document
else None
)
item = self.component_items.get(component_id)
if component is not None and item is not None:
item.sync_name_label(component)
def refresh_graph_item(self, item_kind: str, item_id: str) -> None:
if item_kind == "connection":
if item_kind in {"connection", "connection_data"}:
self.update_connection(item_id)
return
annotation = self.controller.active_graph.annotations.get(item_id)
@@ -908,6 +1020,7 @@ class GraphScene(QGraphicsScene):
start, end = geometry
path, direction_points = self._route_path(start, end, waypoints)
graphics.set_connection_path(path, direction_points)
graphics.sync_name_label(connection)
graphics.set_waypoints(waypoints)
def update_annotation(self, annotation_id: str) -> None: