new text mode editor

This commit is contained in:
2026-07-20 14:24:47 +02:00
parent bf2512995f
commit a067c85994
14 changed files with 685 additions and 392 deletions

View File

@@ -0,0 +1 @@
"""Reusable editing widgets."""

View File

@@ -0,0 +1,193 @@
from copy import deepcopy
from uuid import uuid4
from PySide6.QtCore import Qt, Signal
from PySide6.QtWidgets import QComboBox, QHeaderView, QTableWidgetItem, QWidget
from bedit.core.model import Parameter, Port
from bedit.core.port_types import PortTypeRegistry
from bedit.gui.generated.ui_text_definition_editor import Ui_TextDefinitionEditor
ID_ROLE = Qt.ItemDataRole.UserRole
PROPERTIES_ROLE = Qt.ItemDataRole.UserRole + 1
class TextDefinitionEditor(QWidget):
"""Editor for a text component's equations, ports, and parameters."""
modifiedChanged = Signal(bool)
definitionEdited = Signal()
def __init__(self, parent=None) -> None:
super().__init__(parent)
self.ui = Ui_TextDefinitionEditor()
self.ui.setupUi(self)
self._modified = False
self._loading = False
self.ui.portsTable.horizontalHeader().setSectionResizeMode(
QHeaderView.ResizeMode.Stretch
)
self.ui.parametersTable.horizontalHeader().setSectionResizeMode(
QHeaderView.ResizeMode.Stretch
)
self.ui.columnSplitter.setSizes([560, 340])
self.ui.definitionSplitter.setSizes([300, 300])
self.ui.equationsEdit.textChanged.connect(self._mark_modified)
self.ui.portsTable.cellChanged.connect(self._mark_modified)
self.ui.parametersTable.cellChanged.connect(self._mark_modified)
self.ui.addPortButton.clicked.connect(self.add_port)
self.ui.removePortButton.clicked.connect(self.remove_port)
self.ui.addParameterButton.clicked.connect(self.add_parameter)
self.ui.removeParameterButton.clicked.connect(self.remove_parameter)
self.ui.portsTable.itemSelectionChanged.connect(self._update_buttons)
self.ui.parametersTable.itemSelectionChanged.connect(self._update_buttons)
self._update_buttons()
@property
def is_modified(self) -> bool:
return self._modified
@property
def equations(self) -> str:
return self.ui.equationsEdit.toPlainText()
@property
def ports(self) -> tuple[list[Port], list[Port]]:
inputs: list[Port] = []
outputs: list[Port] = []
for row in range(self.ui.portsTable.rowCount()):
name_item = self.ui.portsTable.item(row, 0)
type_combo = self.ui.portsTable.cellWidget(row, 1)
orientation_combo = self.ui.portsTable.cellWidget(row, 2)
port = Port(
id=name_item.data(ID_ROLE),
name=name_item.text().strip(),
type=type_combo.currentData(),
properties=deepcopy(name_item.data(PROPERTIES_ROLE) or {}),
)
target = inputs if orientation_combo.currentData() == "input" else outputs
target.append(port)
return inputs, outputs
@property
def parameters(self) -> list[Parameter]:
table = self.ui.parametersTable
return [
Parameter(
id=table.item(row, 0).data(ID_ROLE),
name=table.item(row, 0).text().strip(),
type=table.item(row, 1).text().strip(),
value=table.item(row, 2).text(),
)
for row in range(table.rowCount())
]
def set_definition(
self,
equations: str,
inputs: list[Port],
outputs: list[Port],
parameters: list[Parameter],
) -> None:
self._loading = True
self.ui.equationsEdit.setPlainText(equations)
self.ui.portsTable.setRowCount(0)
for port in inputs:
self._append_port(port, "input")
for port in outputs:
self._append_port(port, "output")
self.ui.parametersTable.setRowCount(0)
for parameter in parameters:
self._append_parameter(parameter)
self._loading = False
self.set_modified(False)
self._update_buttons()
def set_modified(self, modified: bool) -> None:
if self._modified != modified:
self._modified = modified
self.modifiedChanged.emit(modified)
def _mark_modified(self, *_args) -> None:
if not self._loading:
self.set_modified(True)
self.definitionEdited.emit()
def _new_combo(self, values: list[tuple[str, str]], current: str) -> QComboBox:
combo = QComboBox(self)
for label, value in values:
combo.addItem(label, value)
combo.setCurrentIndex(max(0, combo.findData(current)))
combo.currentIndexChanged.connect(self._mark_modified)
return combo
def _append_port(self, port: Port, orientation: str) -> None:
table = self.ui.portsTable
row = table.rowCount()
table.insertRow(row)
name = QTableWidgetItem(port.name)
name.setData(ID_ROLE, port.id)
name.setData(PROPERTIES_ROLE, deepcopy(port.properties))
table.setItem(row, 0, name)
types = [(item.display_name, item.id) for item in PortTypeRegistry.all()]
table.setCellWidget(row, 1, self._new_combo(types, port.type))
table.setCellWidget(
row,
2,
self._new_combo([("Input", "input"), ("Output", "output")], orientation),
)
def _append_parameter(self, parameter: Parameter) -> None:
table = self.ui.parametersTable
row = table.rowCount()
table.insertRow(row)
name = QTableWidgetItem(parameter.name)
name.setData(ID_ROLE, parameter.id)
table.setItem(row, 0, name)
table.setItem(row, 1, QTableWidgetItem(parameter.type))
table.setItem(row, 2, QTableWidgetItem(parameter.value))
def add_port(self) -> None:
port = Port(
id=f"port-{uuid4().hex[:8]}",
name=f"Port {self.ui.portsTable.rowCount() + 1}",
type="signal",
properties={"iconPosition": {"x": 0.0, "y": 0.0}},
)
self._loading = True
self._append_port(port, "input")
self._loading = False
self.ui.portsTable.selectRow(self.ui.portsTable.rowCount() - 1)
self._mark_modified()
def remove_port(self) -> None:
row = self.ui.portsTable.currentRow()
if row >= 0:
self.ui.portsTable.removeRow(row)
self._mark_modified()
self._update_buttons()
def add_parameter(self) -> None:
parameter = Parameter(
id=f"parameter-{uuid4().hex[:8]}",
name=f"Parameter {self.ui.parametersTable.rowCount() + 1}",
)
self._loading = True
self._append_parameter(parameter)
self._loading = False
self.ui.parametersTable.selectRow(self.ui.parametersTable.rowCount() - 1)
self._mark_modified()
def remove_parameter(self) -> None:
row = self.ui.parametersTable.currentRow()
if row >= 0:
self.ui.parametersTable.removeRow(row)
self._mark_modified()
self._update_buttons()
def _update_buttons(self) -> None:
self.ui.removePortButton.setEnabled(self.ui.portsTable.currentRow() >= 0)
self.ui.removeParameterButton.setEnabled(
self.ui.parametersTable.currentRow() >= 0
)