Files
BondGraph/BEdit/src/bedit/gui/dialogs/port_options.py

123 lines
5.0 KiB
Python

from copy import deepcopy
from uuid import uuid4
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QDialog, QDialogButtonBox, QListWidgetItem, QMessageBox
from bedit.core.model import Component, Port
from bedit.core.port_types import PortTypeRegistry
from bedit.gui.generated.ui_port_options_dialog import Ui_PortOptionsDialog
PORT_ROLE = Qt.ItemDataRole.UserRole
class PortOptionsDialog(QDialog):
"""Unified editor for a component's typed, oriented ports."""
def __init__(self, component: Component, parent=None, *, read_only: bool = False) -> None:
super().__init__(parent)
self.ui = Ui_PortOptionsDialog()
self.ui.setupUi(self)
self.setWindowTitle(f"Port Options — {component.name}")
self.ports: list[tuple[Port, str]] = [
*((deepcopy(port), "input") for port in component.inputs),
*((deepcopy(port), "output") for port in component.outputs),
]
self._loading = False
self.ui.typeCombo.clear()
for port_type in PortTypeRegistry.all():
self.ui.typeCombo.addItem(port_type.display_name, port_type.id)
self.ui.orientationCombo.setItemData(0, "input")
self.ui.orientationCombo.setItemData(1, "output")
self.ui.portList.currentRowChanged.connect(self._load_current)
self.ui.addPortButton.clicked.connect(self.add_port)
self.ui.removePortButton.clicked.connect(self.remove_port)
self.ui.nameEdit.textEdited.connect(self._store_current)
self.ui.typeCombo.currentIndexChanged.connect(self._store_current)
self.ui.orientationCombo.currentIndexChanged.connect(self._store_current)
self.ui.portSplitter.setSizes([250, 370])
if read_only:
self.ui.addPortButton.setEnabled(False)
self.ui.removePortButton.setEnabled(False)
self.ui.nameEdit.setReadOnly(True)
self.ui.typeCombo.setEnabled(False)
self.ui.orientationCombo.setEnabled(False)
self.ui.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setText("Close")
self.ui.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).hide()
self._rebuild_list(0 if self.ports else -1)
@property
def inputs(self) -> list[Port]:
return [port for port, orientation in self.ports if orientation == "input"]
@property
def outputs(self) -> list[Port]:
return [port for port, orientation in self.ports if orientation == "output"]
def _rebuild_list(self, row: int = -1) -> None:
self.ui.portList.clear()
for port, orientation in self.ports:
item = QListWidgetItem(f"{port.name} [{orientation}, {port.type}]")
item.setData(PORT_ROLE, port.id)
self.ui.portList.addItem(item)
self.ui.portList.setCurrentRow(min(row, len(self.ports) - 1))
self._update_enabled()
def _load_current(self, row: int) -> None:
self._loading = True
enabled = 0 <= row < len(self.ports)
if enabled:
port, orientation = self.ports[row]
self.ui.nameEdit.setText(port.name)
self.ui.typeCombo.setCurrentIndex(self.ui.typeCombo.findData(port.type))
self.ui.orientationCombo.setCurrentIndex(self.ui.orientationCombo.findData(orientation))
else:
self.ui.nameEdit.clear()
self._loading = False
self._update_enabled()
def _update_enabled(self) -> None:
enabled = self.ui.portList.currentRow() >= 0
self.ui.removePortButton.setEnabled(enabled)
self.ui.nameEdit.setEnabled(enabled)
self.ui.typeCombo.setEnabled(enabled)
self.ui.orientationCombo.setEnabled(enabled)
def _store_current(self) -> None:
row = self.ui.portList.currentRow()
if self._loading or not (0 <= row < len(self.ports)):
return
port, _orientation = self.ports[row]
port.name = self.ui.nameEdit.text()
port.type = self.ui.typeCombo.currentData()
self.ports[row] = (port, self.ui.orientationCombo.currentData())
self.ui.portList.item(row).setText(
f"{port.name} [{self.ports[row][1]}, {port.type}]"
)
def add_port(self) -> None:
port = Port(
id=f"port-{uuid4().hex[:8]}",
name=f"Port {len(self.ports) + 1}",
properties={"iconPosition": {"x": 0.0, "y": 0.0}},
type="signal",
)
self.ports.append((port, "input"))
self._rebuild_list(len(self.ports) - 1)
self.ui.nameEdit.selectAll()
self.ui.nameEdit.setFocus()
def remove_port(self) -> None:
row = self.ui.portList.currentRow()
if row >= 0:
self.ports.pop(row)
self._rebuild_list(min(row, len(self.ports) - 1))
def accept(self) -> None:
self._store_current()
if any(not port.name.strip() for port, _orientation in self.ports):
QMessageBox.warning(self, "Invalid port", "Every port must have a name.")
return
super().accept()