Labels
This commit is contained in:
24
src/bedit_gui/commands/graph_label_command.py
Normal file
24
src/bedit_gui/commands/graph_label_command.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
|
from PySide6.QtGui import QUndoCommand
|
||||||
|
|
||||||
|
from bedit_core.models import ComponentID
|
||||||
|
from bedit_gui.models import GraphComponentLabel
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeGraphComponentLabelCommand(QUndoCommand):
|
||||||
|
def __init__(self, document: object, graph_id: ComponentID, component_id: ComponentID, label: GraphComponentLabel, text: str) -> None:
|
||||||
|
super().__init__(text)
|
||||||
|
self.document = document
|
||||||
|
self.graph_id = graph_id
|
||||||
|
self.component_id = component_id
|
||||||
|
database = document._graph_database(False)
|
||||||
|
graph = database.graphs.get(graph_id) if database is not None else None
|
||||||
|
self.old_label = deepcopy(graph.component_labels.get(component_id)) if graph is not None and component_id in graph.component_labels else None
|
||||||
|
self.new_label = deepcopy(label)
|
||||||
|
|
||||||
|
def redo(self) -> None:
|
||||||
|
self.document._set_graph_component_label(self.graph_id, self.component_id, self.new_label)
|
||||||
|
|
||||||
|
def undo(self) -> None:
|
||||||
|
self.document._set_graph_component_label(self.graph_id, self.component_id, self.old_label)
|
||||||
@@ -9,7 +9,7 @@ from PySide6.QtWidgets import QAbstractItemView, QDialog, QHeaderView, QMenu
|
|||||||
from bedit_core.models import Component, ComponentID, ConnectionID, EquationImplementation, GraphImplementation, Port, PortID, Parameter, ParameterID
|
from bedit_core.models import Component, ComponentID, ConnectionID, EquationImplementation, GraphImplementation, Port, PortID, Parameter, ParameterID
|
||||||
from bedit_core.models import Document as CoreDocument
|
from bedit_core.models import Document as CoreDocument
|
||||||
from bedit_gui.documents import Document
|
from bedit_gui.documents import Document
|
||||||
from bedit_gui.models import Graph, Icon
|
from bedit_gui.models import Graph, GraphComponentLabel, Icon
|
||||||
from bedit_gui.views.dialogs.interface_editor_dialog import InterfaceEditorDialog
|
from bedit_gui.views.dialogs.interface_editor_dialog import InterfaceEditorDialog
|
||||||
from bedit_gui.views.dialogs.param_editor_dialog import ParamEditorDialog
|
from bedit_gui.views.dialogs.param_editor_dialog import ParamEditorDialog
|
||||||
from bedit_gui.views.icon_editor_window import IconEditorWindow
|
from bedit_gui.views.icon_editor_window import IconEditorWindow
|
||||||
@@ -63,11 +63,13 @@ class DocumentTreeController(QObject):
|
|||||||
document.model_changed.connect(self._on_document_changed)
|
document.model_changed.connect(self._on_document_changed)
|
||||||
document.icon_changed.connect(self._on_icon_changed)
|
document.icon_changed.connect(self._on_icon_changed)
|
||||||
document.graph_component_position_changed.connect(self._on_graph_component_position_changed)
|
document.graph_component_position_changed.connect(self._on_graph_component_position_changed)
|
||||||
|
document.graph_component_label_changed.connect(self._on_graph_component_label_changed)
|
||||||
document.graph_connection_points_changed.connect(self._on_graph_connection_points_changed)
|
document.graph_connection_points_changed.connect(self._on_graph_connection_points_changed)
|
||||||
document.equation_text_changed.connect(self._on_equation_text_changed)
|
document.equation_text_changed.connect(self._on_equation_text_changed)
|
||||||
self.model.rename_document_requested.connect(self.document.rename)
|
self.model.rename_document_requested.connect(self.document.rename)
|
||||||
self.model.rename_component_requested.connect(self.document.rename_component)
|
self.model.rename_component_requested.connect(self.document.rename_component)
|
||||||
window.graph_editor.component_move_requested.connect(self.document.move_graph_component)
|
window.graph_editor.component_move_requested.connect(self.document.move_graph_component)
|
||||||
|
window.graph_editor.component_label_move_requested.connect(self.document.move_graph_component_label)
|
||||||
window.graph_editor.component_context_menu_requested.connect(self._show_graph_component_context_menu)
|
window.graph_editor.component_context_menu_requested.connect(self._show_graph_component_context_menu)
|
||||||
window.graph_editor.component_open_requested.connect(self._open_graph_component)
|
window.graph_editor.component_open_requested.connect(self._open_graph_component)
|
||||||
window.graph_editor.connection_points_change_requested.connect(self.document.change_graph_connection_points)
|
window.graph_editor.connection_points_change_requested.connect(self.document.change_graph_connection_points)
|
||||||
@@ -155,6 +157,11 @@ class DocumentTreeController(QObject):
|
|||||||
if graph_component is not None and self.document.component_id(graph_component) == graph_id:
|
if graph_component is not None and self.document.component_id(graph_component) == graph_id:
|
||||||
self.window.graph_editor.set_component_position(component_id, position)
|
self.window.graph_editor.set_component_position(component_id, position)
|
||||||
|
|
||||||
|
def _on_graph_component_label_changed(self, graph_id: ComponentID, component_id: ComponentID, label: object) -> None:
|
||||||
|
graph_component = self.window.graph_editor.component()
|
||||||
|
if graph_component is not None and self.document.component_id(graph_component) == graph_id:
|
||||||
|
self.window.graph_editor.set_component_label(component_id, label if isinstance(label, GraphComponentLabel) else None)
|
||||||
|
|
||||||
def _on_graph_connection_points_changed(self, graph_id: ComponentID, connection_id: ConnectionID, points: list[tuple[int, int]] | None) -> None:
|
def _on_graph_connection_points_changed(self, graph_id: ComponentID, connection_id: ConnectionID, points: list[tuple[int, int]] | None) -> None:
|
||||||
graph_component = self.window.graph_editor.component()
|
graph_component = self.window.graph_editor.component()
|
||||||
if graph_component is not None and self.document.component_id(graph_component) == graph_id:
|
if graph_component is not None and self.document.component_id(graph_component) == graph_id:
|
||||||
@@ -177,7 +184,7 @@ class DocumentTreeController(QObject):
|
|||||||
def _show_graph_component_context_menu(self, component_id: ComponentID, global_position: QPoint) -> None:
|
def _show_graph_component_context_menu(self, component_id: ComponentID, global_position: QPoint) -> None:
|
||||||
component = self._components.get(component_id)
|
component = self._components.get(component_id)
|
||||||
if component is not None:
|
if component is not None:
|
||||||
self._show_component_context_menu(component, global_position)
|
self._show_component_context_menu(component, global_position, component_id)
|
||||||
|
|
||||||
def _open_graph_component(self, component_id: ComponentID) -> None:
|
def _open_graph_component(self, component_id: ComponentID) -> None:
|
||||||
index = self.model.component_index(component_id)
|
index = self.model.component_index(component_id)
|
||||||
@@ -185,11 +192,16 @@ class DocumentTreeController(QObject):
|
|||||||
self.window.ui.documentTree.selectionModel().setCurrentIndex(index, QItemSelectionModel.SelectionFlag.ClearAndSelect | QItemSelectionModel.SelectionFlag.Rows)
|
self.window.ui.documentTree.selectionModel().setCurrentIndex(index, QItemSelectionModel.SelectionFlag.ClearAndSelect | QItemSelectionModel.SelectionFlag.Rows)
|
||||||
self.window.ui.documentTree.scrollTo(index)
|
self.window.ui.documentTree.scrollTo(index)
|
||||||
|
|
||||||
def _show_component_context_menu(self, component: Component, global_position: QPoint) -> None:
|
def _show_component_context_menu(self, component: Component, global_position: QPoint, graph_component_id: ComponentID | None = None) -> None:
|
||||||
menu = QMenu(self.window.ui.documentTree)
|
menu = QMenu(self.window.ui.documentTree)
|
||||||
edit_interface = menu.addAction("Edit Interface")
|
edit_interface = menu.addAction("Edit Interface")
|
||||||
edit_params = menu.addAction("Edit Parameters")
|
edit_params = menu.addAction("Edit Parameters")
|
||||||
edit_icon = menu.addAction("Edit Icon")
|
edit_icon = menu.addAction("Edit Icon")
|
||||||
|
show_label = None
|
||||||
|
if graph_component_id is not None:
|
||||||
|
show_label = menu.addAction("Show Label")
|
||||||
|
show_label.setCheckable(True)
|
||||||
|
show_label.setChecked(self.window.graph_editor.component_label_visible(graph_component_id))
|
||||||
menu.addSeparator()
|
menu.addSeparator()
|
||||||
add_graph_component = None
|
add_graph_component = None
|
||||||
add_equation_component = None
|
add_equation_component = None
|
||||||
@@ -205,6 +217,10 @@ class DocumentTreeController(QObject):
|
|||||||
self._edit_params(component)
|
self._edit_params(component)
|
||||||
elif selected is edit_icon:
|
elif selected is edit_icon:
|
||||||
self._edit_icon(component)
|
self._edit_icon(component)
|
||||||
|
elif show_label is not None and selected is show_label:
|
||||||
|
graph_component = self.window.graph_editor.component()
|
||||||
|
if graph_component is not None:
|
||||||
|
self.document.set_graph_component_label_visible(graph_component, graph_component_id, show_label.isChecked())
|
||||||
elif add_graph_component is not None and selected is add_graph_component:
|
elif add_graph_component is not None and selected is add_graph_component:
|
||||||
self._add_graph_component(component)
|
self._add_graph_component(component)
|
||||||
elif add_equation_component is not None and selected is add_equation_component:
|
elif add_equation_component is not None and selected is add_equation_component:
|
||||||
|
|||||||
@@ -15,13 +15,14 @@ from bedit_gui.commands.equation_text_command import ChangeEquationTextCommand
|
|||||||
from bedit_gui.commands.graph_position_command import MoveGraphComponentCommand
|
from bedit_gui.commands.graph_position_command import MoveGraphComponentCommand
|
||||||
from bedit_gui.commands.graph_connection_points_command import ChangeGraphConnectionPointsCommand
|
from bedit_gui.commands.graph_connection_points_command import ChangeGraphConnectionPointsCommand
|
||||||
from bedit_gui.commands.graph_connection_command import AddGraphConnectionCommand, DeleteGraphConnectionCommand
|
from bedit_gui.commands.graph_connection_command import AddGraphConnectionCommand, DeleteGraphConnectionCommand
|
||||||
|
from bedit_gui.commands.graph_label_command import ChangeGraphComponentLabelCommand
|
||||||
from bedit_gui.commands.port_commands import AddPortCommand, ChangePortCommand, RemovePortCommand
|
from bedit_gui.commands.port_commands import AddPortCommand, ChangePortCommand, RemovePortCommand
|
||||||
from bedit_gui.commands.param_commands import AddParamCommand, ChangeParamCommand, RemoveParamCommand
|
from bedit_gui.commands.param_commands import AddParamCommand, ChangeParamCommand, RemoveParamCommand
|
||||||
from bedit_gui.commands.rename_component_command import RenameComponentCommand
|
from bedit_gui.commands.rename_component_command import RenameComponentCommand
|
||||||
from bedit_gui.commands.rename_document_command import RenameDocumentCommand
|
from bedit_gui.commands.rename_document_command import RenameDocumentCommand
|
||||||
from bedit_gui.commands.simulation_database_command import ChangeSimulationDatabaseCommand
|
from bedit_gui.commands.simulation_database_command import ChangeSimulationDatabaseCommand
|
||||||
from bedit_gui.commands.component_command import AddEmptyEquationComponent, AddEmptyGraphComponent, DeleteComponent, PasteComponents
|
from bedit_gui.commands.component_command import AddEmptyEquationComponent, AddEmptyGraphComponent, DeleteComponent, PasteComponents
|
||||||
from bedit_gui.models import Graph, GraphConnection, GraphDatabase, Icon, IconDatabase, Simulation, SimulationDatabase
|
from bedit_gui.models import Graph, GraphComponentLabel, GraphConnection, GraphDatabase, Icon, IconDatabase, Simulation, SimulationDatabase
|
||||||
from bedit_gui.services import document_files
|
from bedit_gui.services import document_files
|
||||||
|
|
||||||
|
|
||||||
@@ -35,6 +36,7 @@ class Document(QObject):
|
|||||||
equation_text_changed = Signal(object, str)
|
equation_text_changed = Signal(object, str)
|
||||||
simulation_database_changed = Signal(object)
|
simulation_database_changed = Signal(object)
|
||||||
graph_component_position_changed = Signal(object, object, object)
|
graph_component_position_changed = Signal(object, object, object)
|
||||||
|
graph_component_label_changed = Signal(object, object, object)
|
||||||
graph_connection_points_changed = Signal(object, object, object)
|
graph_connection_points_changed = Signal(object, object, object)
|
||||||
|
|
||||||
def __init__(self, parent: QObject | None = None) -> None:
|
def __init__(self, parent: QObject | None = None) -> None:
|
||||||
@@ -157,6 +159,36 @@ class Document(QObject):
|
|||||||
graph.component_positions[component_id] = position
|
graph.component_positions[component_id] = position
|
||||||
self.graph_component_position_changed.emit(graph_id, component_id, position)
|
self.graph_component_position_changed.emit(graph_id, component_id, position)
|
||||||
|
|
||||||
|
def move_graph_component_label(self, graph_component: Component, component_id: ComponentID, relative_position: tuple[int, int]) -> None:
|
||||||
|
graph_id = self.component_id(graph_component)
|
||||||
|
database = self._graph_database(False)
|
||||||
|
graph = database.graphs.get(graph_id) if database is not None else None
|
||||||
|
current = deepcopy(graph.component_labels.get(component_id)) if graph is not None and component_id in graph.component_labels else GraphComponentLabel()
|
||||||
|
if current.relative_position != relative_position:
|
||||||
|
current.relative_position = relative_position
|
||||||
|
self.undo_stack.push(ChangeGraphComponentLabelCommand(self, graph_id, component_id, current, "Move component label"))
|
||||||
|
|
||||||
|
def set_graph_component_label_visible(self, graph_component: Component, component_id: ComponentID, visible: bool) -> None:
|
||||||
|
graph_id = self.component_id(graph_component)
|
||||||
|
database = self._graph_database(False)
|
||||||
|
graph = database.graphs.get(graph_id) if database is not None else None
|
||||||
|
current = deepcopy(graph.component_labels.get(component_id)) if graph is not None and component_id in graph.component_labels else GraphComponentLabel()
|
||||||
|
if current.visible != visible:
|
||||||
|
current.visible = visible
|
||||||
|
self.undo_stack.push(ChangeGraphComponentLabelCommand(self, graph_id, component_id, current, "Show component label" if visible else "Hide component label"))
|
||||||
|
|
||||||
|
def _set_graph_component_label(self, graph_id: ComponentID, component_id: ComponentID, label: GraphComponentLabel | None) -> None:
|
||||||
|
if label is None:
|
||||||
|
database = self._graph_database(False)
|
||||||
|
graph = database.graphs.get(graph_id) if database is not None else None
|
||||||
|
if graph is not None:
|
||||||
|
graph.component_labels.pop(component_id, None)
|
||||||
|
else:
|
||||||
|
database = self._graph_database(True)
|
||||||
|
graph = database.graphs.setdefault(graph_id, Graph())
|
||||||
|
graph.component_labels[component_id] = deepcopy(label)
|
||||||
|
self.graph_component_label_changed.emit(graph_id, component_id, deepcopy(label))
|
||||||
|
|
||||||
def _graph_database(self, create: bool) -> GraphDatabase | None:
|
def _graph_database(self, create: bool) -> GraphDatabase | None:
|
||||||
metadata = self.model.metadata
|
metadata = self.model.metadata
|
||||||
value = metadata.get("graph_database") if metadata is not None else None
|
value = metadata.get("graph_database") if metadata is not None else None
|
||||||
|
|||||||
@@ -134,23 +134,39 @@ class GraphConnection:
|
|||||||
def to_data(self) -> dict[str, Any]:
|
def to_data(self) -> dict[str, Any]:
|
||||||
return {"points": [list(point) for point in self.points]}
|
return {"points": [list(point) for point in self.points]}
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class GraphComponentLabel:
|
||||||
|
relative_position: tuple[int, int] = (0, 8)
|
||||||
|
visible: bool = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_data(cls, data: Mapping[str, Any]) -> GraphComponentLabel:
|
||||||
|
position = data.get("relative_position", [0, 8])
|
||||||
|
return cls(relative_position=(int(position[0]), int(position[1])), visible=bool(data.get("visible", True)))
|
||||||
|
|
||||||
|
def to_data(self) -> dict[str, Any]:
|
||||||
|
return {"relative_position": list(self.relative_position), "visible": self.visible}
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Graph:
|
class Graph:
|
||||||
shapes: dict[ShapeID, Shape] = field(default_factory=dict)
|
shapes: dict[ShapeID, Shape] = field(default_factory=dict)
|
||||||
component_positions: dict[ComponentID, tuple[int, int]] = field(default_factory=dict)
|
component_positions: dict[ComponentID, tuple[int, int]] = field(default_factory=dict)
|
||||||
connections: dict[ConnectionID, GraphConnection] = field(default_factory=dict)
|
connections: dict[ConnectionID, GraphConnection] = field(default_factory=dict)
|
||||||
|
component_labels: dict[ComponentID, GraphComponentLabel] = field(default_factory=dict)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_data(cls, data: Mapping[str, Any]) -> Graph:
|
def from_data(cls, data: Mapping[str, Any]) -> Graph:
|
||||||
shapes = {ShapeID(key): Shape.from_data(value) for key, value in data.get("shapes", {}).items()}
|
shapes = {ShapeID(key): Shape.from_data(value) for key, value in data.get("shapes", {}).items()}
|
||||||
component_positions = {ComponentID(key): (int(value[0]), int(value[1])) for key, value in data.get("component_positions", {}).items()}
|
component_positions = {ComponentID(key): (int(value[0]), int(value[1])) for key, value in data.get("component_positions", {}).items()}
|
||||||
|
component_labels = {ComponentID(key): GraphComponentLabel.from_data(value) for key, value in data.get("component_labels", {}).items()}
|
||||||
connections = {ConnectionID(key): GraphConnection.from_data(value) for key, value in data.get("connections", {}).items()}
|
connections = {ConnectionID(key): GraphConnection.from_data(value) for key, value in data.get("connections", {}).items()}
|
||||||
return cls(shapes=shapes, component_positions=component_positions, connections=connections)
|
return cls(shapes=shapes, component_positions=component_positions, component_labels=component_labels, connections=connections)
|
||||||
|
|
||||||
def to_data(self) -> dict[str, Any]:
|
def to_data(self) -> dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"shapes": {str(key): shape.to_data() for key, shape in self.shapes.items()},
|
"shapes": {str(key): shape.to_data() for key, shape in self.shapes.items()},
|
||||||
"component_positions": {str(key): list(position) for key, position in self.component_positions.items()},
|
"component_positions": {str(key): list(position) for key, position in self.component_positions.items()},
|
||||||
|
"component_labels": {str(key): label.to_data() for key, label in self.component_labels.items()},
|
||||||
"connections": {str(key): connection.to_data() for key, connection in self.connections.items()},
|
"connections": {str(key): connection.to_data() for key, connection in self.connections.items()},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ from math import hypot
|
|||||||
|
|
||||||
from PySide6.QtCore import QEvent, QObject, QPointF, QRectF, QSize, QTimer, Qt, Signal
|
from PySide6.QtCore import QEvent, QObject, QPointF, QRectF, QSize, QTimer, Qt, Signal
|
||||||
from PySide6.QtGui import QActionGroup, QBrush, QColor, QCursor, QKeySequence, QMouseEvent, QPainter, QPainterPath, QPen, QShortcut, QWheelEvent
|
from PySide6.QtGui import QActionGroup, QBrush, QColor, QCursor, QKeySequence, QMouseEvent, QPainter, QPainterPath, QPen, QShortcut, QWheelEvent
|
||||||
from PySide6.QtWidgets import QGraphicsEllipseItem, QGraphicsItem, QGraphicsPathItem, QGraphicsPixmapItem, QGraphicsScene, QGraphicsSceneMouseEvent, QGraphicsView, QMenu, QWidget
|
from PySide6.QtWidgets import QGraphicsEllipseItem, QGraphicsItem, QGraphicsPathItem, QGraphicsPixmapItem, QGraphicsScene, QGraphicsSceneMouseEvent, QGraphicsTextItem, QGraphicsView, QMenu, QWidget
|
||||||
|
|
||||||
from bedit_core.models import BondCausality, BondConnection, BondPort, Component, ComponentID, Connection, ConnectionID, GraphImplementation, Port, PortID, SignalConnection, SignalDirection, SignalPort
|
from bedit_core.models import BondCausality, BondConnection, BondPort, Component, ComponentID, Connection, ConnectionID, GraphImplementation, Port, PortID, SignalConnection, SignalDirection, SignalPort
|
||||||
from bedit_gui.models import Graph, GraphConnection, Icon
|
from bedit_gui.models import Graph, GraphComponentLabel, GraphConnection, Icon
|
||||||
from bedit_gui.ui.generated.ui_graph_editor_widget import Ui_graphEditorWidget
|
from bedit_gui.ui.generated.ui_graph_editor_widget import Ui_graphEditorWidget
|
||||||
from bedit_gui.utils.icon import get_pixmap_bounding_box, render_icon
|
from bedit_gui.utils.icon import get_pixmap_bounding_box, render_icon
|
||||||
|
|
||||||
@@ -20,6 +20,7 @@ MAX_ZOOM = 4.0
|
|||||||
ZOOM_STEP = 1.15
|
ZOOM_STEP = 1.15
|
||||||
ZOOM_TO_FIT_PADDING = 32.0
|
ZOOM_TO_FIT_PADDING = 32.0
|
||||||
COMPONENT_ICON_SIZE = QSize(128, 128)
|
COMPONENT_ICON_SIZE = QSize(128, 128)
|
||||||
|
COMPONENT_LABEL_FONT_SIZE = 24.0
|
||||||
FALLBACK_COMPONENT_SPACING = 128
|
FALLBACK_COMPONENT_SPACING = 128
|
||||||
CONNECTION_WIDTH = 4.0
|
CONNECTION_WIDTH = 4.0
|
||||||
CONNECTION_BOUNDING_BOX_SPACING = 16.0
|
CONNECTION_BOUNDING_BOX_SPACING = 16.0
|
||||||
@@ -184,6 +185,58 @@ class GraphComponentItem(QGraphicsPixmapItem):
|
|||||||
event.accept()
|
event.accept()
|
||||||
|
|
||||||
|
|
||||||
|
class GraphComponentLabelItem(QGraphicsTextItem):
|
||||||
|
def __init__(self, component_id: ComponentID, text: str, label: GraphComponentLabel, component_item: GraphComponentItem, editor: GraphEditorWidget) -> None:
|
||||||
|
super().__init__(text, component_item)
|
||||||
|
self.component_id = component_id
|
||||||
|
self.editor = editor
|
||||||
|
self._dragging = False
|
||||||
|
self._drag_start = label.relative_position
|
||||||
|
font = self.font()
|
||||||
|
font.setItalic(True)
|
||||||
|
font.setPointSizeF(COMPONENT_LABEL_FONT_SIZE)
|
||||||
|
self.setFont(font)
|
||||||
|
self.setDefaultTextColor(QColor("#202020"))
|
||||||
|
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable)
|
||||||
|
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable, editor.mode is GraphEditorMode.NORMAL)
|
||||||
|
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges)
|
||||||
|
self.set_relative_position(label.relative_position)
|
||||||
|
|
||||||
|
def relative_position(self) -> tuple[int, int]:
|
||||||
|
component_bounds = self.editor._component_bounds[self.component_id]
|
||||||
|
return round(self.pos().x() + self.boundingRect().width() / 2), round(self.pos().y() - component_bounds.bottom())
|
||||||
|
|
||||||
|
def set_relative_position(self, position: tuple[int, int]) -> None:
|
||||||
|
component_bounds = self.editor._component_bounds[self.component_id]
|
||||||
|
self.setPos(position[0] - self.boundingRect().width() / 2, component_bounds.bottom() + position[1])
|
||||||
|
|
||||||
|
def itemChange(self, change: QGraphicsItem.GraphicsItemChange, value: object) -> object:
|
||||||
|
if change == QGraphicsItem.GraphicsItemChange.ItemPositionChange and self._dragging and isinstance(value, QPointF):
|
||||||
|
component_bounds = self.editor._component_bounds[self.component_id]
|
||||||
|
size = self.editor.snap_to_grid_size
|
||||||
|
relative_x = value.x() + self.boundingRect().width() / 2
|
||||||
|
relative_y = value.y() - component_bounds.bottom()
|
||||||
|
value = QPointF(round(relative_x / size) * size - self.boundingRect().width() / 2, component_bounds.bottom() + round(relative_y / size) * size)
|
||||||
|
return super().itemChange(change, value)
|
||||||
|
|
||||||
|
def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None:
|
||||||
|
self._drag_start = self.relative_position()
|
||||||
|
self._dragging = True
|
||||||
|
super().mousePressEvent(event)
|
||||||
|
|
||||||
|
def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None:
|
||||||
|
super().mouseReleaseEvent(event)
|
||||||
|
self._dragging = False
|
||||||
|
position = self.relative_position()
|
||||||
|
self.set_relative_position(position)
|
||||||
|
if position != self._drag_start:
|
||||||
|
self.editor.finish_component_label_move(self.component_id, position)
|
||||||
|
|
||||||
|
def contextMenuEvent(self, event) -> None:
|
||||||
|
self.editor.component_context_menu_requested.emit(self.component_id, event.screenPos())
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
|
||||||
class GraphConnectionPointItem(QGraphicsEllipseItem):
|
class GraphConnectionPointItem(QGraphicsEllipseItem):
|
||||||
def __init__(self, connection_id: ConnectionID, index: int, position: tuple[int, int], editor: GraphEditorWidget) -> None:
|
def __init__(self, connection_id: ConnectionID, index: int, position: tuple[int, int], editor: GraphEditorWidget) -> None:
|
||||||
radius = CONNECTION_WIDTH
|
radius = CONNECTION_WIDTH
|
||||||
@@ -234,6 +287,7 @@ class GraphEditorWidget(QWidget):
|
|||||||
component_move_requested = Signal(object, object, object)
|
component_move_requested = Signal(object, object, object)
|
||||||
component_context_menu_requested = Signal(object, object)
|
component_context_menu_requested = Signal(object, object)
|
||||||
component_open_requested = Signal(object)
|
component_open_requested = Signal(object)
|
||||||
|
component_label_move_requested = Signal(object, object, object)
|
||||||
connection_points_change_requested = Signal(object, object, object, str)
|
connection_points_change_requested = Signal(object, object, object, str)
|
||||||
connection_add_requested = Signal(object, object)
|
connection_add_requested = Signal(object, object)
|
||||||
connections_delete_requested = Signal(object, object)
|
connections_delete_requested = Signal(object, object)
|
||||||
@@ -251,6 +305,7 @@ class GraphEditorWidget(QWidget):
|
|||||||
self._connection_preview: QGraphicsPathItem | None = None
|
self._connection_preview: QGraphicsPathItem | None = None
|
||||||
self._component_items: dict[ComponentID, GraphComponentItem] = {}
|
self._component_items: dict[ComponentID, GraphComponentItem] = {}
|
||||||
self._component_bounds: dict[ComponentID, QRectF] = {}
|
self._component_bounds: dict[ComponentID, QRectF] = {}
|
||||||
|
self._component_label_items: dict[ComponentID, GraphComponentLabelItem] = {}
|
||||||
self._connection_items: dict[ConnectionID, GraphConnectionItem] = {}
|
self._connection_items: dict[ConnectionID, GraphConnectionItem] = {}
|
||||||
self._connection_point_items: dict[ConnectionID, list[GraphConnectionPointItem]] = {}
|
self._connection_point_items: dict[ConnectionID, list[GraphConnectionPointItem]] = {}
|
||||||
self.set_snap_to_grid_size(snap_to_grid_size)
|
self.set_snap_to_grid_size(snap_to_grid_size)
|
||||||
@@ -310,6 +365,7 @@ class GraphEditorWidget(QWidget):
|
|||||||
self._icons = icons or {}
|
self._icons = icons or {}
|
||||||
self._component_items = {}
|
self._component_items = {}
|
||||||
self._component_bounds = {}
|
self._component_bounds = {}
|
||||||
|
self._component_label_items = {}
|
||||||
self._connection_items = {}
|
self._connection_items = {}
|
||||||
self._connection_point_items = {}
|
self._connection_point_items = {}
|
||||||
self.scene.clear()
|
self.scene.clear()
|
||||||
@@ -331,6 +387,11 @@ class GraphEditorWidget(QWidget):
|
|||||||
self._component_bounds[component_id] = bounds.translated(-pixmap.width() / 2, -pixmap.height() / 2)
|
self._component_bounds[component_id] = bounds.translated(-pixmap.width() / 2, -pixmap.height() / 2)
|
||||||
self.scene.addItem(item)
|
self.scene.addItem(item)
|
||||||
|
|
||||||
|
for component_id, child in component.implementation.graph.components.items():
|
||||||
|
label = graph.component_labels.get(component_id, GraphComponentLabel())
|
||||||
|
if label.visible:
|
||||||
|
self._create_component_label_item(component_id, child.name, label)
|
||||||
|
|
||||||
for connection_id, connection in component.implementation.graph.connections.items():
|
for connection_id, connection in component.implementation.graph.connections.items():
|
||||||
if not isinstance(connection, (SignalConnection, BondConnection)):
|
if not isinstance(connection, (SignalConnection, BondConnection)):
|
||||||
continue
|
continue
|
||||||
@@ -459,6 +520,31 @@ class GraphEditorWidget(QWidget):
|
|||||||
if self._component is not None:
|
if self._component is not None:
|
||||||
self.component_move_requested.emit(self._component, component_id, position)
|
self.component_move_requested.emit(self._component, component_id, position)
|
||||||
|
|
||||||
|
def finish_component_label_move(self, component_id: ComponentID, relative_position: tuple[int, int]) -> None:
|
||||||
|
if self._component is not None:
|
||||||
|
self.component_label_move_requested.emit(self._component, component_id, relative_position)
|
||||||
|
|
||||||
|
def component_label_visible(self, component_id: ComponentID) -> bool:
|
||||||
|
return self._graph.component_labels.get(component_id, GraphComponentLabel()).visible
|
||||||
|
|
||||||
|
def set_component_label(self, component_id: ComponentID, label: GraphComponentLabel | None) -> None:
|
||||||
|
if label is None:
|
||||||
|
self._graph.component_labels.pop(component_id, None)
|
||||||
|
label = GraphComponentLabel()
|
||||||
|
else:
|
||||||
|
self._graph.component_labels[component_id] = label
|
||||||
|
item = self._component_label_items.pop(component_id, None)
|
||||||
|
if item is not None:
|
||||||
|
item.setParentItem(None)
|
||||||
|
self.scene.removeItem(item)
|
||||||
|
component = self._component
|
||||||
|
if component is not None and label.visible and component_id in component.implementation.graph.components:
|
||||||
|
self._create_component_label_item(component_id, component.implementation.graph.components[component_id].name, label)
|
||||||
|
|
||||||
|
def _create_component_label_item(self, component_id: ComponentID, text: str, label: GraphComponentLabel) -> None:
|
||||||
|
item = GraphComponentLabelItem(component_id, text, label, self._component_items[component_id], self)
|
||||||
|
self._component_label_items[component_id] = item
|
||||||
|
|
||||||
def set_component_position(self, component_id: ComponentID, position: tuple[int, int] | None) -> None:
|
def set_component_position(self, component_id: ComponentID, position: tuple[int, int] | None) -> None:
|
||||||
item = self._component_items.get(component_id)
|
item = self._component_items.get(component_id)
|
||||||
if item is None:
|
if item is None:
|
||||||
|
|||||||
@@ -628,6 +628,29 @@
|
|||||||
384
|
384
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"component_labels": {
|
||||||
|
"43b3aee6-0b38-429b-9c6d-d38fc097297f": {
|
||||||
|
"relative_position": [
|
||||||
|
68,
|
||||||
|
-68
|
||||||
|
],
|
||||||
|
"visible": true
|
||||||
|
},
|
||||||
|
"9ee2f42b-5ed1-446d-8790-c2a1df6b61d3": {
|
||||||
|
"relative_position": [
|
||||||
|
0,
|
||||||
|
8
|
||||||
|
],
|
||||||
|
"visible": false
|
||||||
|
},
|
||||||
|
"9f392c28-6bef-4b10-9305-93e250747007": {
|
||||||
|
"relative_position": [
|
||||||
|
0,
|
||||||
|
8
|
||||||
|
],
|
||||||
|
"visible": false
|
||||||
|
}
|
||||||
|
},
|
||||||
"connections": {
|
"connections": {
|
||||||
"7e293c73-1e07-4d4c-b750-6f43870c71c4": {
|
"7e293c73-1e07-4d4c-b750-6f43870c71c4": {
|
||||||
"points": [
|
"points": [
|
||||||
|
|||||||
Reference in New Issue
Block a user