labels
This commit is contained in:
@@ -200,6 +200,19 @@ class EditComponentAppearanceCommand(QUndoCommand):
|
||||
self.controller._set_component_appearance(self.component_id, self.old)
|
||||
|
||||
|
||||
class EditComponentPropertiesCommand(QUndoCommand):
|
||||
def __init__(self, controller, component_id: str, old: dict, new: dict, text: str) -> None:
|
||||
super().__init__(text)
|
||||
self.controller, self.component_id = controller, component_id
|
||||
self.old, self.new = old, new
|
||||
|
||||
def redo(self) -> None:
|
||||
self.controller._set_component_properties(self.component_id, self.new)
|
||||
|
||||
def undo(self) -> None:
|
||||
self.controller._set_component_properties(self.component_id, self.old)
|
||||
|
||||
|
||||
class RenameInterfacePortCommand(QUndoCommand):
|
||||
def __init__(self, controller, owner_id: str, port_id: str, old: str, new: str) -> None:
|
||||
super().__init__("Rename interface port")
|
||||
|
||||
@@ -15,6 +15,7 @@ from bedit.gui.controllers.commands import (
|
||||
EditGraphItemCommand,
|
||||
EditTextDefinitionCommand,
|
||||
EditComponentAppearanceCommand,
|
||||
EditComponentPropertiesCommand,
|
||||
MoveComponentCommand,
|
||||
MoveInterfacePortCommand,
|
||||
PasteSelectionCommand,
|
||||
@@ -45,6 +46,7 @@ class DocumentController(QObject):
|
||||
componentRemoved = Signal(str)
|
||||
componentMoved = Signal(str, QPointF)
|
||||
componentRotated = Signal(str, float)
|
||||
componentPropertiesChanged = Signal(str)
|
||||
connectionAdded = Signal(str)
|
||||
connectionRemoved = Signal(str)
|
||||
graphItemChanged = Signal(str, str)
|
||||
@@ -516,6 +518,7 @@ class DocumentController(QObject):
|
||||
inputs: list[Port],
|
||||
outputs: list[Port],
|
||||
show_subtree: bool,
|
||||
show_name: bool,
|
||||
) -> None:
|
||||
if self.document is None:
|
||||
return
|
||||
@@ -528,13 +531,20 @@ class DocumentController(QObject):
|
||||
"inputs": [port.to_dict() for port in component.inputs],
|
||||
"outputs": [port.to_dict() for port in component.outputs],
|
||||
"show_subtree": component.show_subtree_in_library,
|
||||
"properties": deepcopy(component.properties),
|
||||
}
|
||||
properties = deepcopy(component.properties)
|
||||
was_visible = bool(properties.get("showName", False))
|
||||
properties["showName"] = show_name
|
||||
if show_name and not was_visible:
|
||||
properties.pop("nameLabelPosition", None)
|
||||
new = {
|
||||
"name": name,
|
||||
"icon": icon.to_dict(),
|
||||
"inputs": [port.to_dict() for port in inputs],
|
||||
"outputs": [port.to_dict() for port in outputs],
|
||||
"show_subtree": show_subtree,
|
||||
"properties": properties,
|
||||
}
|
||||
if old != new:
|
||||
candidate = deepcopy(self.document)
|
||||
@@ -543,9 +553,75 @@ class DocumentController(QObject):
|
||||
candidate_component.icon = Icon.from_dict(icon.to_dict())
|
||||
candidate_component.inputs = deepcopy(inputs)
|
||||
candidate_component.outputs = deepcopy(outputs)
|
||||
candidate_component.properties = deepcopy(properties)
|
||||
candidate.validate()
|
||||
self.undo_stack.push(EditComponentAppearanceCommand(self, component_id, old, new))
|
||||
|
||||
def move_component_name_label(self, component_id: str, position: QPointF) -> None:
|
||||
if self.document is None:
|
||||
return
|
||||
component = self.document.find_component(component_id)
|
||||
if component is None:
|
||||
return
|
||||
old = deepcopy(component.properties)
|
||||
new = deepcopy(old)
|
||||
new["nameLabelPosition"] = {"x": position.x(), "y": position.y()}
|
||||
if old != new:
|
||||
self.undo_stack.push(
|
||||
EditComponentPropertiesCommand(
|
||||
self, component_id, old, new, "Move component name"
|
||||
)
|
||||
)
|
||||
|
||||
def edit_connection_options(
|
||||
self, connection_id: str, name: str, show_name: bool
|
||||
) -> None:
|
||||
owner = self.active_component
|
||||
if owner is None or self.active_component_id is None:
|
||||
return
|
||||
connection = owner.graph.connections.get(connection_id)
|
||||
if connection is None:
|
||||
return
|
||||
old = {"name": connection.name, "properties": deepcopy(connection.properties)}
|
||||
properties = deepcopy(connection.properties)
|
||||
was_visible = bool(properties.get("showName", False))
|
||||
properties["showName"] = show_name
|
||||
if show_name and not was_visible:
|
||||
properties.pop("nameLabelPosition", None)
|
||||
new = {"name": name, "properties": properties}
|
||||
if old != new:
|
||||
self.undo_stack.push(
|
||||
EditGraphItemCommand(
|
||||
self,
|
||||
self.active_component_id,
|
||||
"connection_data",
|
||||
connection_id,
|
||||
old,
|
||||
new,
|
||||
"Edit connection options",
|
||||
)
|
||||
)
|
||||
|
||||
def move_connection_name_label(self, connection_id: str, position: QPointF) -> None:
|
||||
connection = self.active_graph.connections.get(connection_id)
|
||||
if connection is None or self.active_component_id is None:
|
||||
return
|
||||
old = deepcopy(connection.properties)
|
||||
new = deepcopy(old)
|
||||
new["nameLabelPosition"] = {"x": position.x(), "y": position.y()}
|
||||
if old != new:
|
||||
self.undo_stack.push(
|
||||
EditGraphItemCommand(
|
||||
self,
|
||||
self.active_component_id,
|
||||
"connection",
|
||||
connection_id,
|
||||
old,
|
||||
new,
|
||||
"Move connection name",
|
||||
)
|
||||
)
|
||||
|
||||
def edit_component_ports(
|
||||
self, component_id: str, inputs: list[Port], outputs: list[Port]
|
||||
) -> None:
|
||||
@@ -585,6 +661,7 @@ class DocumentController(QObject):
|
||||
"inputs": [port.to_dict() for port in component.inputs],
|
||||
"outputs": [port.to_dict() for port in component.outputs],
|
||||
"show_subtree": component.show_subtree_in_library,
|
||||
"properties": deepcopy(component.properties),
|
||||
}
|
||||
new = {
|
||||
**old,
|
||||
@@ -750,7 +827,12 @@ class DocumentController(QObject):
|
||||
self, owner_id: str, item_kind: str, item_id: str, values: dict
|
||||
) -> None:
|
||||
graph = self._graph_for(owner_id)
|
||||
if item_kind == "connection":
|
||||
if item_kind == "connection_data":
|
||||
item = graph.connections.get(item_id)
|
||||
if item is not None:
|
||||
item.name = values["name"]
|
||||
item.properties = deepcopy(values["properties"])
|
||||
elif item_kind == "connection":
|
||||
item = graph.connections.get(item_id)
|
||||
if item is not None:
|
||||
item.properties = deepcopy(values)
|
||||
@@ -867,10 +949,19 @@ class DocumentController(QObject):
|
||||
component.inputs = [Port.from_dict(port) for port in values["inputs"]]
|
||||
component.outputs = [Port.from_dict(port) for port in values["outputs"]]
|
||||
component.show_subtree_in_library = values["show_subtree"]
|
||||
component.properties = deepcopy(values["properties"])
|
||||
self.documentReset.emit()
|
||||
if component_id == self.active_component_id:
|
||||
self.activeGraphChanged.emit()
|
||||
|
||||
def _set_component_properties(self, component_id: str, properties: dict) -> None:
|
||||
if self.document is None:
|
||||
return
|
||||
component = self.document.find_component(component_id)
|
||||
if component is not None:
|
||||
component.properties = deepcopy(properties)
|
||||
self.componentPropertiesChanged.emit(component_id)
|
||||
|
||||
def _delete_items(
|
||||
self,
|
||||
owner_id: str | None,
|
||||
|
||||
@@ -17,6 +17,7 @@ class ComponentOptionsDialog(QDialog):
|
||||
self.ui.nameEdit.setText(component.name)
|
||||
self.ui.editIconButton.clicked.connect(self.edit_icon)
|
||||
self.ui.showSubtreeCheckBox.setChecked(component.show_subtree_in_library)
|
||||
self.ui.showNameCheckBox.setChecked(bool(component.properties.get("showName", False)))
|
||||
|
||||
def edit_icon(self) -> None:
|
||||
working = Component.from_dict(self.component.to_dict())
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog,
|
||||
QDialogButtonBox,
|
||||
QCheckBox,
|
||||
QFormLayout,
|
||||
QLineEdit,
|
||||
QMessageBox,
|
||||
@@ -11,7 +12,15 @@ from PySide6.QtWidgets import (
|
||||
class ItemOptionsDialog(QDialog):
|
||||
"""Small, extensible options dialog shared by ports and connections."""
|
||||
|
||||
def __init__(self, title: str, name: str, parent=None, *, name_required: bool = True) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
title: str,
|
||||
name: str,
|
||||
parent=None,
|
||||
*,
|
||||
name_required: bool = True,
|
||||
show_name: bool | None = None,
|
||||
) -> None:
|
||||
super().__init__(parent)
|
||||
self.name_required = name_required
|
||||
self.setWindowTitle(title)
|
||||
@@ -21,6 +30,11 @@ class ItemOptionsDialog(QDialog):
|
||||
self.form = QFormLayout()
|
||||
self.name_edit = QLineEdit(name, self)
|
||||
self.form.addRow("Name:", self.name_edit)
|
||||
self.show_name_check = None
|
||||
if show_name is not None:
|
||||
self.show_name_check = QCheckBox("Show name below connection", self)
|
||||
self.show_name_check.setChecked(show_name)
|
||||
self.form.addRow("", self.show_name_check)
|
||||
layout.addLayout(self.form)
|
||||
|
||||
buttons = QDialogButtonBox(
|
||||
@@ -35,6 +49,10 @@ class ItemOptionsDialog(QDialog):
|
||||
def name(self) -> str:
|
||||
return self.name_edit.text().strip()
|
||||
|
||||
@property
|
||||
def show_name(self) -> bool:
|
||||
return bool(self.show_name_check and self.show_name_check.isChecked())
|
||||
|
||||
def accept(self) -> None:
|
||||
if self.name_required and not self.name:
|
||||
QMessageBox.warning(self, "Invalid name", "The name cannot be empty.")
|
||||
|
||||
@@ -55,6 +55,11 @@ class Ui_ComponentOptionsDialog(object):
|
||||
|
||||
self.optionsForm.setWidget(2, QFormLayout.ItemRole.SpanningRole, self.showSubtreeCheckBox)
|
||||
|
||||
self.showNameCheckBox = QCheckBox(ComponentOptionsDialog)
|
||||
self.showNameCheckBox.setObjectName(u"showNameCheckBox")
|
||||
|
||||
self.optionsForm.setWidget(3, QFormLayout.ItemRole.SpanningRole, self.showNameCheckBox)
|
||||
|
||||
|
||||
self.dialogLayout.addLayout(self.optionsForm)
|
||||
|
||||
@@ -85,5 +90,6 @@ class Ui_ComponentOptionsDialog(object):
|
||||
self.editIconButton.setToolTip(QCoreApplication.translate("ComponentOptionsDialog", u"Open the vector icon and port-position editor", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.showSubtreeCheckBox.setText(QCoreApplication.translate("ComponentOptionsDialog", u"Show contained components in the Libraries tree", None))
|
||||
self.showNameCheckBox.setText(QCoreApplication.translate("ComponentOptionsDialog", u"Show name below component", None))
|
||||
# retranslateUi
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -131,6 +131,7 @@ class MainWindow(QMainWindow):
|
||||
self.ui.actionPaste.triggered.connect(self.ui.graphView.paste_selection)
|
||||
self.ui.actionSelectAll.triggered.connect(self.ui.graphView.select_all)
|
||||
self.ui.actionRotateClockwise.triggered.connect(self.ui.graphView.rotate_selected)
|
||||
self.addAction(self.ui.actionRotateClockwise)
|
||||
self.ui.actionZoomIn.triggered.connect(self.ui.graphView.zoom_in)
|
||||
self.ui.actionZoomOut.triggered.connect(self.ui.graphView.zoom_out)
|
||||
self.ui.actionCenterView.triggered.connect(self.ui.graphView.center_workspace)
|
||||
@@ -527,6 +528,7 @@ class MainWindow(QMainWindow):
|
||||
dialog.edited_inputs,
|
||||
dialog.edited_outputs,
|
||||
dialog.ui.showSubtreeCheckBox.isChecked(),
|
||||
dialog.ui.showNameCheckBox.isChecked(),
|
||||
)
|
||||
|
||||
@Slot(str, str)
|
||||
@@ -550,9 +552,17 @@ class MainWindow(QMainWindow):
|
||||
connection = owner.graph.connections.get(connection_id)
|
||||
if connection is None:
|
||||
return
|
||||
dialog = ItemOptionsDialog("Connection Options", connection.name, self, name_required=False)
|
||||
dialog = ItemOptionsDialog(
|
||||
"Connection Options",
|
||||
connection.name,
|
||||
self,
|
||||
name_required=False,
|
||||
show_name=bool(connection.properties.get("showName", False)),
|
||||
)
|
||||
if dialog.exec() == dialog.DialogCode.Accepted:
|
||||
self.document_controller.rename_connection(connection_id, dialog.name)
|
||||
self.document_controller.edit_connection_options(
|
||||
connection_id, dialog.name, dialog.show_name
|
||||
)
|
||||
|
||||
@Slot()
|
||||
def show_about(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user