Parameter editor added

This commit is contained in:
2026-07-27 12:07:47 +02:00
parent 36051e1577
commit 713d094b08
8 changed files with 474 additions and 12 deletions

View File

@@ -25,14 +25,18 @@ def main() -> int:
settings = ApplicationSettings()
document = Document(app)
window = MainWindow()
LogController(window, settings.log_level)
DocumentController(document, window)
SettingsController(window, settings)
UndoController(document, window)
ViewMenuController(window)
DocumentTreeController(document, window)
window_state_controller = WindowStateController(app, window)
window_state_controller.restore()
document.new()
window.showMaximized()
return app.exec()

View File

@@ -0,0 +1,76 @@
from __future__ import annotations
from copy import deepcopy
from PySide6.QtGui import QUndoCommand
from bedit_core.models import Component, Parameter, ParameterID
class AddParamCommand(QUndoCommand):
def __init__(
self,
document: object,
component: Component,
param_id: ParameterID,
param: Parameter,
) -> None:
super().__init__("Add port")
self.document = document
self.component = component
self.param_id = param_id
self.param = deepcopy(param)
def redo(self) -> None:
self.component.parameters[self.param_id] = deepcopy(self.param)
self.document.model_changed.emit(self.document.model)
def undo(self) -> None:
del self.component.parameters[self.param_id]
self.document.model_changed.emit(self.document.model)
class RemoveParamCommand(QUndoCommand):
def __init__(
self,
document: object,
component: Component,
param_id: ParameterID,
) -> None:
super().__init__("Remove parameter")
self.document = document
self.component = component
self.param_id = param_id
self.param = deepcopy(component.parameters[param_id])
def redo(self) -> None:
del self.component.parameters[self.param_id]
self.document.model_changed.emit(self.document.model)
def undo(self) -> None:
self.component.parameters[self.param_id] = deepcopy(self.param)
self.document.model_changed.emit(self.document.model)
class ChangeParamCommand(QUndoCommand):
def __init__(
self,
document: object,
component: Component,
param_id: ParameterID,
param: Parameter,
) -> None:
super().__init__("Change parameter")
self.document = document
self.component = component
self.param_id = param_id
self.old_param = deepcopy(component.parameters[param_id])
self.new_param = deepcopy(param)
def redo(self) -> None:
self.component.parameters[self.param_id] = deepcopy(self.new_param)
self.document.model_changed.emit(self.document.model)
def undo(self) -> None:
self.component.parameters[self.param_id] = deepcopy(self.old_param)
self.document.model_changed.emit(self.document.model)

View File

@@ -4,27 +4,34 @@ from typing import Protocol
from PySide6.QtCore import QObject, QPoint, Qt
from PySide6.QtWidgets import QDialog, QMenu
from bedit_core.models import Component, Port, PortID
from bedit_core.models import Component, Port, PortID, Parameter, ParameterID
from bedit_core.models import Document as CoreDocument
from bedit_gui.documents import Document
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.main_window import MainWindow
from bedit_gui.views.models.document_tree_model import DocumentTreeModel
class InterfaceEditorLike(Protocol):
def exec(self) -> int: ...
def ports(self) -> dict[PortID, Port]: ...
class ParamEditorLike(Protocol):
def exec(self) -> int: ...
def params(self) -> dict[ParameterID, Parameter]: ...
InterfaceEditorFactory = Callable[
[dict[PortID, Port], MainWindow],
InterfaceEditorLike,
]
ParamEditorFactory = Callable[
[dict[ParameterID, Parameter], MainWindow],
ParamEditorLike
]
class DocumentTreeController(QObject):
def __init__(
@@ -32,6 +39,7 @@ class DocumentTreeController(QObject):
document: Document,
window: MainWindow,
interface_editor_factory: InterfaceEditorFactory = InterfaceEditorDialog,
param_editor_factory: ParamEditorFactory = ParamEditorDialog,
) -> None:
super().__init__(window)
@@ -39,6 +47,7 @@ class DocumentTreeController(QObject):
self.window = window
self.model = DocumentTreeModel()
self.interface_editor_factory = interface_editor_factory
self.param_editor_factory = param_editor_factory
window.ui.documentTree.setModel(self.model)
document.model_changed.connect(self._on_document_changed)
@@ -71,11 +80,14 @@ class DocumentTreeController(QObject):
menu = QMenu(self.window.ui.documentTree)
edit_interface = menu.addAction("Edit Interface")
edit_params = menu.addAction("Edit Parameters")
selected = menu.exec(
self.window.ui.documentTree.viewport().mapToGlobal(position)
)
if selected is edit_interface:
self._edit_interface(component)
elif selected is edit_params:
self._edit_params(component)
def _edit_interface(self, component: Component) -> None:
dialog = self.interface_editor_factory(
@@ -84,3 +96,12 @@ class DocumentTreeController(QObject):
)
if dialog.exec() == QDialog.DialogCode.Accepted:
self.document.update_component_ports(component, dialog.ports())
def _edit_params(self, component: Component) -> None:
dialog = self.param_editor_factory(
component.parameters,
self.window
)
if dialog.exec() == QDialog.DialogCode.Accepted:
self.document.update_component_params(component, dialog.params())

View File

@@ -5,9 +5,10 @@ from pathlib import Path
from PySide6.QtCore import QObject, Signal
from PySide6.QtGui import QUndoStack
from bedit_core.models import ID, Component, Port, PortID
from bedit_core.models import ID, Component, Port, PortID, Parameter, ParameterID
from bedit_core.models import Document as CoreDocument
from bedit_gui.commands.port_commands import AddPortCommand, ChangePortCommand, RemovePortCommand
from bedit_gui.commands.param_commands import AddParamCommand, ChangeParamCommand, RemoveParamCommand
from bedit_gui.commands.rename_component_command import RenameComponentCommand
from bedit_gui.commands.rename_document_command import RenameDocumentCommand
from bedit_gui.services import document_files
@@ -88,11 +89,7 @@ class Document(QObject):
def rename_component(self, component: Component, name: str) -> None:
self.undo_stack.push(RenameComponentCommand(self, component, name))
def update_component_ports(
self,
component: Component,
ports: dict[PortID, Port],
) -> None:
def update_component_ports(self, component: Component, ports: dict[PortID, Port]) -> None:
current = component.interface.ports
removed = [
RemovePortCommand(self, component, port_id)
@@ -115,3 +112,28 @@ class Document(QObject):
for command in commands:
self.undo_stack.push(command)
self.undo_stack.endMacro()
def update_component_params(self, component: Component, params: dict[ParameterID, Parameter]) -> None:
current = component.parameters
removed = [
RemoveParamCommand(self, component, param_id)
for param_id in current.keys() - params.keys()
]
added = [
AddParamCommand(self, component, param_id, params[param_id])
for param_id in params.keys() - current.keys()
]
changed = [
ChangeParamCommand(self, component, param_id, params[param_id])
for param_id in current.keys() & params.keys()
if current[param_id] != params[param_id]
]
commands = [*removed, *added, *changed]
if not commands:
return
self.undo_stack.beginMacro("Edit parameter")
for command in commands:
self.undo_stack.push(command)
self.undo_stack.endMacro()

View File

@@ -0,0 +1,169 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ParamEditor</class>
<widget class="QWidget" name="ParamEditor">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>541</width>
<height>241</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QVBoxLayout" name="leftColumn">
<item>
<widget class="QListView" name="portList"/>
</item>
<item>
<layout class="QHBoxLayout" name="buttonRow">
<item>
<widget class="QPushButton" name="addParam">
<property name="text">
<string>Add Parameter</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeParam">
<property name="text">
<string>Remove Parameter</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="rightColumn">
<item>
<layout class="QFormLayout" name="basicForm">
<item row="0" column="0">
<widget class="QLabel" name="nameLabel">
<property name="text">
<string>Name:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="nameEdit"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="typeLabel">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="typeRow">
<item>
<widget class="QComboBox" name="typeComboBox"/>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="sizeLabel">
<property name="text">
<string>Size:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="sizeRow">
<item>
<widget class="QSpinBox" name="widthSize">
<property name="suffix">
<string> rows</string>
</property>
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="heightSize">
<property name="suffix">
<string> columns</string>
</property>
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
</layout>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Quantity:</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Unit:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="valueEdit"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="valueLabel">
<property name="text">
<string>Value:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QFormLayout" name="descriptionForm">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::FieldGrowthPolicy::AllNonFixedFieldsGrow</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="descriptionLabel">
<property name="text">
<string>Description:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QPlainTextEdit" name="descriptionEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>60</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,36 @@
from __future__ import annotations
from PySide6.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QWidget
from bedit_core.models import ParameterID, Parameter
from bedit_gui.views.param_editor_widget import ParamEditorWidget
class ParamEditorDialog(QDialog):
"""Modal wrapper around the reusable param editor widget."""
def __init__(
self,
params: dict[ParameterID, Parameter],
parent: QWidget | None = None,
) -> None:
super().__init__(parent)
self.setWindowTitle("Edit Parameters")
self.resize(550, 280)
layout = QVBoxLayout(self)
self.editor = ParamEditorWidget(self)
self.editor.set_params(params)
layout.addWidget(self.editor)
buttons = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel,
self,
)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
layout.addWidget(buttons)
def params(self) -> dict[ParameterID, Parameter]:
return self.editor.params()

View File

@@ -0,0 +1,134 @@
from __future__ import annotations
from copy import deepcopy
from PySide6.QtCore import QItemSelection, Signal
from PySide6.QtGui import QStandardItem, QStandardItemModel
from PySide6.QtWidgets import QWidget
from bedit_core.models import Parameter, ParameterID, ValueType
from bedit_gui.ui.generated.ui_param_editor_widget import Ui_ParamEditor
class ParamEditorWidget(QWidget):
"""Reusable editor for a detached draft of a component's parameters."""
params_changed = Signal()
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent)
self.ui = Ui_ParamEditor()
self.ui.setupUi(self)
self._params: dict[ParameterID, Parameter] = {}
self._param_ids: list[ParameterID] = []
self._loading = False
self.ui.typeComboBox.addItem("Real", ValueType.REAL)
self.ui.typeComboBox.addItem("Integer", ValueType.INT)
self.ui.typeComboBox.addItem("Boolean", ValueType.BOOL)
self._list_model = QStandardItemModel(self)
self.ui.portList.setModel(self._list_model)
self.ui.portList.selectionModel().selectionChanged.connect(self._selection_changed)
self.ui.addParam.clicked.connect(self._add_param)
self.ui.removeParam.clicked.connect(self._remove_param)
self.ui.nameEdit.textEdited.connect(self._form_changed)
self.ui.typeComboBox.currentIndexChanged.connect(self._form_changed)
self.ui.valueEdit.textEdited.connect(self._form_changed)
self.ui.widthSize.valueChanged.connect(self._form_changed)
self.ui.heightSize.valueChanged.connect(self._form_changed)
self.ui.descriptionEdit.textChanged.connect(self._form_changed)
self._set_editor_enabled(False)
def set_params(self, params: dict[ParameterID, Parameter]) -> None:
self._params = deepcopy(params)
self._param_ids = list(self._params)
self._rebuild_list()
def params(self) -> dict[ParameterID, Parameter]:
return deepcopy(self._params)
def _rebuild_list(self, selected_id: ParameterID | None = None) -> None:
self._list_model.clear()
for param_id in self._param_ids:
self._list_model.appendRow(QStandardItem(self._params[param_id].name))
if self._param_ids:
if selected_id not in self._params:
selected_id = self._param_ids[0]
index = self._list_model.index(self._param_ids.index(selected_id), 0)
self.ui.portList.setCurrentIndex(index)
else:
self._set_editor_enabled(False)
self.ui.removeParam.setEnabled(False)
def _selection_changed(self, _selected: QItemSelection, _deselected: QItemSelection) -> None:
param_id = self._selected_param_id()
self.ui.removeParam.setEnabled(param_id is not None)
self._set_editor_enabled(param_id is not None)
if param_id is not None:
self._load_param(self._params[param_id])
def _selected_param_id(self) -> ParameterID | None:
index = self.ui.portList.currentIndex()
if not index.isValid() or index.row() >= len(self._param_ids):
return None
return self._param_ids[index.row()]
def _load_param(self, param: Parameter) -> None:
self._loading = True
self.ui.nameEdit.setText(param.name)
self.ui.typeComboBox.setCurrentIndex(self.ui.typeComboBox.findData(param.value_type))
self.ui.valueEdit.setText(param.value)
self.ui.widthSize.setValue(param.matrix_size[0])
self.ui.heightSize.setValue(param.matrix_size[1])
self.ui.descriptionEdit.setPlainText(param.description or "")
self._loading = False
def _form_changed(self, *_args: object) -> None:
if self._loading:
return
param_id = self._selected_param_id()
if param_id is None:
return
old_param = self._params[param_id]
self._params[param_id] = Parameter(
name=self.ui.nameEdit.text().strip(),
value_type=self.ui.typeComboBox.currentData(),
value=self.ui.valueEdit.text().strip(),
matrix_size=[self.ui.widthSize.value(), self.ui.heightSize.value()],
quantity=old_param.quantity,
unit=old_param.unit,
description=self.ui.descriptionEdit.toPlainText() or None,
)
self._list_model.item(self._param_ids.index(param_id)).setText(self._params[param_id].name)
self.params_changed.emit()
def _add_param(self) -> None:
param_id = ParameterID()
param = Parameter(name=f"Parameter {len(self._params) + 1}")
self._params[param_id] = param
self._param_ids.append(param_id)
self._rebuild_list(param_id)
self.params_changed.emit()
def _remove_param(self) -> None:
param_id = self._selected_param_id()
if param_id is None:
return
row = self._param_ids.index(param_id)
del self._params[param_id]
self._param_ids.remove(param_id)
selected = self._param_ids[min(row, len(self._param_ids) - 1)] if self._param_ids else None
self._rebuild_list(selected)
self.params_changed.emit()
def _set_editor_enabled(self, enabled: bool) -> None:
for widget in (self.ui.nameEdit, self.ui.typeComboBox, self.ui.widthSize, self.ui.heightSize, self.ui.descriptionEdit):
widget.setEnabled(enabled)