Compare commits
2 Commits
346a963acc
...
748bb08531
| Author | SHA1 | Date | |
|---|---|---|---|
| 748bb08531 | |||
| 9df352f6b7 |
191
AGENTS.md
Normal file
191
AGENTS.md
Normal file
@@ -0,0 +1,191 @@
|
||||
# BEdit Agent Guide
|
||||
|
||||
This file is the handoff context for coding agents working in this repository. Read it before making changes.
|
||||
|
||||
## Working agreement
|
||||
|
||||
- Make only the changes the user requested.
|
||||
- Keep completely out of unrelated code. Do not reformat, reorder, rename, clean up, or “improve” code that is outside the task.
|
||||
- Preserve existing user changes and assume a dirty worktree belongs to the user.
|
||||
- Inspect the relevant files before deciding on an implementation.
|
||||
- Prefer small, modular changes over growing `MainWindow`, a controller, or another file into a monolith.
|
||||
- When the user asks why something happens, diagnose and explain it without editing files unless they also ask for a fix.
|
||||
- Do not create commits unless explicitly requested.
|
||||
|
||||
## Code style
|
||||
|
||||
Follow the local style in the file being edited, with these user preferences taking priority:
|
||||
|
||||
- Prefer compact, readable one-line imports. Do not introduce parenthesized multiline imports.
|
||||
- Avoid spreading short function calls, conditions, and expressions across multiple lines.
|
||||
- Do not run broad formatters or import sorters.
|
||||
- Do not use a lint autofix over the project.
|
||||
- Keep classes and functions focused. Extract a controller, service, command, model, or reusable widget when a feature would otherwise make an existing file large.
|
||||
- Generated modules contain no handwritten application behavior.
|
||||
|
||||
The project uses Ruff, but import sorting is intentionally not enforced during scoped checks:
|
||||
|
||||
```sh
|
||||
.venv/bin/ruff check --ignore I001 path/to/changed_file.py
|
||||
```
|
||||
|
||||
## Project structure and dependency direction
|
||||
|
||||
Relevant GUI structure:
|
||||
|
||||
```text
|
||||
src/bedit_gui/
|
||||
├── application.py
|
||||
├── commands/
|
||||
├── controllers/
|
||||
├── documents/
|
||||
├── models.py
|
||||
├── services/
|
||||
├── ui/
|
||||
│ ├── forms/
|
||||
│ └── generated/
|
||||
├── resources/
|
||||
│ └── generated/
|
||||
├── utils/
|
||||
└── views/
|
||||
└── models/
|
||||
```
|
||||
|
||||
Responsibilities:
|
||||
|
||||
- `application.py`: composition root. Create and connect the application, document, window, services, and controllers here.
|
||||
- `documents/document.py`: editable document facade. Owns the core model, path, main `QUndoStack`, modified state, and high-level operations.
|
||||
- `commands/`: `QUndoCommand` implementations. Persistent changes to the document model go through commands.
|
||||
- `controllers/`: connect actions and widgets to document/service operations. Keep workflow logic out of `MainWindow`.
|
||||
- `views/`: handwritten widget/window/graphics behavior.
|
||||
- `views/models/`: Qt item models used by views.
|
||||
- `services/`: non-visual functionality such as files, clipboard, logging, and settings.
|
||||
- `models.py`: GUI metadata persisted inside the core document, currently including icons and shapes.
|
||||
- `bedit_core`: domain model and serialization. It must never import from `bedit_gui`.
|
||||
|
||||
Preferred direction:
|
||||
|
||||
```text
|
||||
views/controllers
|
||||
↓
|
||||
documents/services
|
||||
↓
|
||||
commands
|
||||
↓
|
||||
bedit_core
|
||||
```
|
||||
|
||||
Avoid introducing imports from `bedit_gui` into `bedit_core` or circular dependencies between services and documents.
|
||||
|
||||
## Qt Designer and generated files
|
||||
|
||||
Raw forms are in:
|
||||
|
||||
```text
|
||||
src/bedit_gui/ui/forms/
|
||||
```
|
||||
|
||||
Generated Python is in:
|
||||
|
||||
```text
|
||||
src/bedit_gui/ui/generated/
|
||||
```
|
||||
|
||||
Never add handwritten behavior to generated UI modules. Change the `.ui` form and regenerate with:
|
||||
|
||||
```sh
|
||||
.venv/bin/python scripts/generate_qt_files.py
|
||||
```
|
||||
|
||||
The generation script also fixes the package-qualified resource import. Calling `pyside6-uic` directly without the script can produce a broken `resources_rc` import.
|
||||
|
||||
Raw resources and the QRC file live under `src/bedit_gui/resources/`. Generated resource Python lives under `src/bedit_gui/resources/generated/`.
|
||||
|
||||
## Undo and document changes
|
||||
|
||||
- The main application document owns the main undo stack.
|
||||
- The icon editor owns a separate local undo stack.
|
||||
- The `Document` facade should expose high-level methods that push commands. Controllers should normally call those methods rather than construct commands.
|
||||
- Commands mutate the model in `redo()`/`undo()` and emit the appropriate document signals.
|
||||
- Multi-object user operations should be one command or one undo macro.
|
||||
- Allocate stable IDs and final names before pushing a command so redo reproduces the same result.
|
||||
- Component names must be unique within their destination component dictionary. Conflicts use `_0`, `_1`, and so on.
|
||||
- Copy/paste must generate new component, port, parameter, connection, and icon-shape IDs and rewrite references.
|
||||
|
||||
## Document tree
|
||||
|
||||
`DocumentTreeModel` has two columns:
|
||||
|
||||
- Column 0: editable document/component name.
|
||||
- Column 1: rendered component icon.
|
||||
|
||||
Use `selectedRows(0)` for multi-selection; `selectedIndexes()` returns both columns. Filter selected descendants when an ancestor is also selected.
|
||||
|
||||
The tree uses extended row selection. Delete, cut, and copy may operate on multiple components. Paste targets either:
|
||||
|
||||
- the document root, or
|
||||
- the component dictionary of a selected graph component.
|
||||
|
||||
The controller listens to document model/icon signals and refreshes the tree/icon cache.
|
||||
|
||||
## Clipboard architecture
|
||||
|
||||
Clipboard support is intentionally extensible:
|
||||
|
||||
- `services/clipboard.py`: system `QClipboard` and MIME/JSON handling.
|
||||
- `services/component_clipboard.py`: component payload serialization and ID remapping.
|
||||
- `controllers/clipboard_controller.py`: focus-based action router and handlers.
|
||||
|
||||
`ClipboardHandler` is the base implementation for future editors. Add a graph-editor handler later by subclassing it and registering that handler in `application.py`.
|
||||
|
||||
Text widgets use their native `copy()`, `cut()`, and `paste()` methods. Component clipboard data uses the custom BEdit MIME type and JSON; never use pickle or live object references.
|
||||
|
||||
## Icon editor conventions
|
||||
|
||||
- Icons are GUI metadata stored in `document.metadata["icon_database"]`.
|
||||
- Shapes currently include rectangles, text, and lines.
|
||||
- Shape changes use the icon editor’s local undo stack.
|
||||
- Shapes are selectable, movable, resizable, pixel-snapped, and constrained to the icon scene.
|
||||
- Ports are separate 16×16 items: black for inputs and white for outputs. They can be moved but are not ordinary deletable/copyable shapes.
|
||||
- Colors are serialized as `#rrggbbaa`.
|
||||
- The icon scene is currently `(-64, -64, 128, 128)`.
|
||||
- The grid spacing is 8 scene pixels and is drawn only inside the scene rectangle.
|
||||
- Keep reusable widgets such as the RGBA color button independent of the icon editor.
|
||||
- Static icon previews belong in rendering utilities, not in the interactive editor scene.
|
||||
|
||||
## Actions and shortcut routing
|
||||
|
||||
- Put visible actions in Designer menus/toolbars.
|
||||
- An action that only exists as a child object may not have an active shortcut; attach it to the relevant widget or place it in a menu/toolbar.
|
||||
- Route application-wide Copy/Cut/Paste by focused widget through `ClipboardController`.
|
||||
- Scope destructive shortcuts to the relevant widget where appropriate.
|
||||
- Always guard the operation itself even when an action is disabled for presentation.
|
||||
|
||||
## Verification
|
||||
|
||||
The old test suite and its VS Code/packaging references were deliberately removed. Do not recreate a test suite unless asked.
|
||||
|
||||
Use checks proportional to the change:
|
||||
|
||||
```sh
|
||||
.venv/bin/ruff check --ignore I001 path/to/changed_files.py
|
||||
```
|
||||
|
||||
For Qt smoke checks in a headless environment:
|
||||
|
||||
```sh
|
||||
QT_QPA_PLATFORM=minimal QT_QPA_PLATFORMTHEME= QT_STYLE_OVERRIDE=Fusion .venv/bin/python ...
|
||||
```
|
||||
|
||||
Prefer focused model, serialization, signal, command undo/redo, and offscreen rendering checks. Do not launch a GUI during verification unless explicitly requested or approved.
|
||||
|
||||
## Handoff expectations
|
||||
|
||||
At the end of a task, report:
|
||||
|
||||
- the outcome,
|
||||
- the files or subsystem changed,
|
||||
- relevant behavior and limitations,
|
||||
- checks that actually passed.
|
||||
|
||||
Do not claim tests passed when only a lint or smoke check was run.
|
||||
53
src/bedit_gui/commands/equation_text_command.py
Normal file
53
src/bedit_gui/commands/equation_text_command.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from PySide6.QtGui import QUndoCommand
|
||||
|
||||
from bedit_core.models import Component, EquationImplementation
|
||||
|
||||
|
||||
class ChangeEquationTextCommand(QUndoCommand):
|
||||
COMMAND_ID = 1001
|
||||
|
||||
def __init__(self, document: object, component: Component, section: str, text: list[str], edit_id: int) -> None:
|
||||
super().__init__(self._command_text(section))
|
||||
implementation = component.implementation
|
||||
if not isinstance(implementation, EquationImplementation):
|
||||
raise TypeError("equation text can only be changed on an equation component")
|
||||
|
||||
self.document = document
|
||||
self.component = component
|
||||
self.section = section
|
||||
self.old_text = list(getattr(implementation, section))
|
||||
self.new_text = list(text)
|
||||
self.edit_id = edit_id
|
||||
|
||||
def id(self) -> int:
|
||||
return self.COMMAND_ID
|
||||
|
||||
def mergeWith(self, other: QUndoCommand) -> bool:
|
||||
if not isinstance(other, ChangeEquationTextCommand):
|
||||
return False
|
||||
if other.component is not self.component or other.section != self.section or other.edit_id != self.edit_id:
|
||||
return False
|
||||
self.new_text = list(other.new_text)
|
||||
return True
|
||||
|
||||
def redo(self) -> None:
|
||||
self._set_text(self.new_text)
|
||||
|
||||
def undo(self) -> None:
|
||||
self._set_text(self.old_text)
|
||||
|
||||
def _set_text(self, text: list[str]) -> None:
|
||||
implementation = self.component.implementation
|
||||
assert isinstance(implementation, EquationImplementation)
|
||||
setattr(implementation, self.section, list(text))
|
||||
self.document.equation_text_changed.emit(self.component, self.section)
|
||||
|
||||
@staticmethod
|
||||
def _command_text(section: str) -> str:
|
||||
return {
|
||||
"declarations": "Edit declarations",
|
||||
"initial_equations": "Edit initial equations",
|
||||
"equations": "Edit equations",
|
||||
}[section]
|
||||
@@ -5,7 +5,7 @@ from typing import Protocol
|
||||
from PySide6.QtCore import QObject, QPoint, QSize, Qt
|
||||
from PySide6.QtWidgets import QAbstractItemView, QDialog, QHeaderView, QMenu
|
||||
|
||||
from bedit_core.models import Component, ComponentID, GraphImplementation, Port, PortID, Parameter, ParameterID
|
||||
from bedit_core.models import Component, ComponentID, EquationImplementation, GraphImplementation, Port, PortID, Parameter, ParameterID
|
||||
from bedit_core.models import Document as CoreDocument
|
||||
from bedit_gui.documents import Document
|
||||
from bedit_gui.models import Icon
|
||||
@@ -57,8 +57,11 @@ class DocumentTreeController(QObject):
|
||||
self._components: dict[ComponentID, Component] = {}
|
||||
|
||||
window.ui.documentTree.setModel(self.model)
|
||||
window.ui.documentTree.selectionModel().selectionChanged.connect(self._selection_changed)
|
||||
window.equation_editor.equation_text_change_requested.connect(self.document.update_component_equation_text)
|
||||
document.model_changed.connect(self._on_document_changed)
|
||||
document.icon_changed.connect(self._on_icon_changed)
|
||||
document.equation_text_changed.connect(self._on_equation_text_changed)
|
||||
self.model.rename_document_requested.connect(self.document.rename)
|
||||
self.model.rename_component_requested.connect(self.document.rename_component)
|
||||
window.ui.actionDelete.triggered.connect(self.delete_selected_component)
|
||||
@@ -87,6 +90,7 @@ class DocumentTreeController(QObject):
|
||||
|
||||
def _on_document_changed(self, model: CoreDocument) -> None:
|
||||
"""Rebuild the tree whenever New/Open replaces the core document."""
|
||||
self._show_equation_component(None)
|
||||
self.model.set_document(model)
|
||||
self._components = {}
|
||||
self._collect_components(model.root)
|
||||
@@ -98,6 +102,23 @@ class DocumentTreeController(QObject):
|
||||
# expanded component IDs and restore only those nodes.
|
||||
self.window.ui.documentTree.expandAll()
|
||||
|
||||
def _selection_changed(self, *_args: object) -> None:
|
||||
indexes = self.window.ui.documentTree.selectionModel().selectedRows(0)
|
||||
component = self.model.value(indexes[0]) if len(indexes) == 1 else None
|
||||
self._show_equation_component(component if isinstance(component, Component) else None)
|
||||
|
||||
def _show_equation_component(self, component: Component | None) -> None:
|
||||
if component is not None and isinstance(component.implementation, EquationImplementation):
|
||||
self.window.equation_editor.set_component(component)
|
||||
self.window.equation_editor.show()
|
||||
else:
|
||||
self.window.equation_editor.set_component(None)
|
||||
self.window.equation_editor.hide()
|
||||
|
||||
def _on_equation_text_changed(self, component: Component, section: str) -> None:
|
||||
if self.window.equation_editor.component() is component:
|
||||
self.window.equation_editor.refresh_text(section)
|
||||
|
||||
def _on_icon_changed(self, component_id: ComponentID, icon: object) -> None:
|
||||
component = self._components.get(component_id)
|
||||
if component is None:
|
||||
|
||||
@@ -31,11 +31,13 @@ class UndoController(QObject):
|
||||
redo_action.setEnabled(undo_stack.canRedo())
|
||||
|
||||
def undo(self) -> None:
|
||||
self.window.equation_editor.finish_text_edit()
|
||||
command = self.document.undo_stack.undoText()
|
||||
self.document.undo_stack.undo()
|
||||
logger.info("Undo: %s", command)
|
||||
|
||||
def redo(self) -> None:
|
||||
self.window.equation_editor.finish_text_edit()
|
||||
command = self.document.undo_stack.redoText()
|
||||
self.document.undo_stack.redo()
|
||||
logger.info("Redo: %s", command)
|
||||
|
||||
@@ -9,6 +9,7 @@ from PySide6.QtGui import QUndoStack
|
||||
from bedit_core.models import ID, Component, ComponentID, GraphImplementation, Port, PortID, Parameter, ParameterID
|
||||
from bedit_core.models import Document as CoreDocument
|
||||
from bedit_gui.commands.change_icon_command import ChangeIconCommand
|
||||
from bedit_gui.commands.equation_text_command import ChangeEquationTextCommand
|
||||
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
|
||||
@@ -25,6 +26,7 @@ class Document(QObject):
|
||||
path_changed = Signal(object)
|
||||
modified_changed = Signal(bool)
|
||||
icon_changed = Signal(object, object)
|
||||
equation_text_changed = Signal(object, str)
|
||||
|
||||
def __init__(self, parent: QObject | None = None) -> None:
|
||||
super().__init__(parent)
|
||||
@@ -197,6 +199,11 @@ class Document(QObject):
|
||||
self.undo_stack.push(command)
|
||||
self.undo_stack.endMacro()
|
||||
|
||||
def update_component_equation_text(self, component: Component, section: str, text: list[str], edit_id: int) -> None:
|
||||
command = ChangeEquationTextCommand(self, component, section, text, edit_id)
|
||||
if command.old_text != command.new_text:
|
||||
self.undo_stack.push(command)
|
||||
|
||||
def add_empty_graph_component(self, component: Component) -> None:
|
||||
if isinstance(component.implementation, GraphImplementation):
|
||||
command = AddEmptyGraphComponent(self, component)
|
||||
|
||||
86
src/bedit_gui/ui/forms/equation_editor_widget.ui
Normal file
86
src/bedit_gui/ui/forms/equation_editor_widget.ui
Normal file
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>equationEditorWidget</class>
|
||||
<widget class="QWidget" name="equationEditorWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1079</width>
|
||||
<height>730</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="declarationEditor">
|
||||
<property name="text">
|
||||
<string>Declarations</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTextEdit" name="declarationsTextEdit"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="initialEquationEditor">
|
||||
<property name="text">
|
||||
<string>Initial equations</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTextEdit" name="initialEquationsTextEdit"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="equationEditor">
|
||||
<property name="text">
|
||||
<string>Equations</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTextEdit" name="equationsTextEdit"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item alignment="Qt::AlignmentFlag::AlignLeft">
|
||||
<widget class="QToolButton" name="sidebarButton">
|
||||
<property name="toolTip">
|
||||
<string>Hide parameter and port editors</string>
|
||||
</property>
|
||||
<property name="arrowType">
|
||||
<enum>Qt::ArrowType::RightArrow</enum>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="paramEditor">
|
||||
<property name="text">
|
||||
<string>paramEditor</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="portEditor">
|
||||
<property name="text">
|
||||
<string>portEditor</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
161
src/bedit_gui/views/equation_editor_widget.py
Normal file
161
src/bedit_gui/views/equation_editor_widget.py
Normal file
@@ -0,0 +1,161 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from PySide6.QtCore import QEvent, QObject, QTimer, Qt, Signal
|
||||
from PySide6.QtWidgets import QWidget
|
||||
|
||||
from bedit_core.models import Component, EquationImplementation
|
||||
from bedit_gui.ui.generated.ui_equation_editor_widget import Ui_equationEditorWidget
|
||||
from bedit_gui.views.param_editor_widget import ParamEditorWidget
|
||||
from bedit_gui.views.port_editor_widget import PortEditorWidget
|
||||
|
||||
|
||||
class EquationEditorWidget(QWidget):
|
||||
"""Editor that keeps an equation component synchronized with its fields."""
|
||||
|
||||
component_changed = Signal(object)
|
||||
equation_text_change_requested = Signal(object, str, object, int)
|
||||
sidebar_visible_changed = Signal(bool)
|
||||
|
||||
def __init__(self, parent: QWidget | None = None) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
self.ui = Ui_equationEditorWidget()
|
||||
self.ui.setupUi(self)
|
||||
self._component: Component | None = None
|
||||
self._loading = False
|
||||
self._edit_id = 0
|
||||
self._edit_timer = QTimer(self)
|
||||
self._edit_timer.setInterval(750)
|
||||
self._edit_timer.setSingleShot(True)
|
||||
self._edit_timer.timeout.connect(self.finish_text_edit)
|
||||
|
||||
self.param_editor = ParamEditorWidget(self)
|
||||
self.port_editor = PortEditorWidget(self)
|
||||
self._replace_placeholder(self.ui.paramEditor, self.param_editor)
|
||||
self._replace_placeholder(self.ui.portEditor, self.port_editor)
|
||||
self.ui.sidebarButton.clicked.connect(self.toggle_sidebar)
|
||||
self.set_sidebar_visible(True)
|
||||
|
||||
for editor in self._text_editors():
|
||||
editor.setUndoRedoEnabled(False)
|
||||
editor.installEventFilter(self)
|
||||
editor.textChanged.connect(self._text_changed)
|
||||
self.param_editor.params_changed.connect(self._params_changed)
|
||||
self.port_editor.ports_changed.connect(self._ports_changed)
|
||||
|
||||
self._set_editors_enabled(False)
|
||||
|
||||
def set_component(self, component: Component | None) -> None:
|
||||
if component is not None and not isinstance(component.implementation, EquationImplementation):
|
||||
raise TypeError("EquationEditorWidget only supports components with an equation implementation")
|
||||
|
||||
self._component = component
|
||||
self.refresh()
|
||||
|
||||
def component(self) -> Component | None:
|
||||
return self._component
|
||||
|
||||
def finish_text_edit(self) -> None:
|
||||
self._edit_timer.stop()
|
||||
self._edit_id += 1
|
||||
|
||||
def toggle_sidebar(self) -> None:
|
||||
self.set_sidebar_visible(not self.param_editor.isVisibleTo(self))
|
||||
|
||||
def set_sidebar_visible(self, visible: bool) -> None:
|
||||
changed = self.param_editor.isVisibleTo(self) != visible
|
||||
self.param_editor.setVisible(visible)
|
||||
self.port_editor.setVisible(visible)
|
||||
self.ui.sidebarButton.setArrowType(Qt.ArrowType.RightArrow if visible else Qt.ArrowType.LeftArrow)
|
||||
self.ui.sidebarButton.setToolTip("Hide parameter and port editors" if visible else "Show parameter and port editors")
|
||||
if changed:
|
||||
self.sidebar_visible_changed.emit(visible)
|
||||
|
||||
def refresh(self) -> None:
|
||||
"""Reload the editors after the component was changed externally."""
|
||||
self._loading = True
|
||||
component = self._component
|
||||
if component is None:
|
||||
self.ui.declarationsTextEdit.clear()
|
||||
self.ui.initialEquationsTextEdit.clear()
|
||||
self.ui.equationsTextEdit.clear()
|
||||
self.param_editor.set_params({})
|
||||
self.port_editor.set_ports({})
|
||||
else:
|
||||
implementation = component.implementation
|
||||
assert isinstance(implementation, EquationImplementation)
|
||||
self.ui.declarationsTextEdit.setPlainText("\n".join(implementation.declarations))
|
||||
self.ui.initialEquationsTextEdit.setPlainText("\n".join(implementation.initial_equations))
|
||||
self.ui.equationsTextEdit.setPlainText("\n".join(implementation.equations))
|
||||
self.param_editor.set_params(component.parameters)
|
||||
self.port_editor.set_ports(component.interface.ports)
|
||||
self._loading = False
|
||||
self._set_editors_enabled(component is not None)
|
||||
|
||||
def refresh_text(self, section: str) -> None:
|
||||
if self._component is None:
|
||||
return
|
||||
implementation = self._component.implementation
|
||||
assert isinstance(implementation, EquationImplementation)
|
||||
editor = self._editor_for_section(section)
|
||||
text = "\n".join(getattr(implementation, section))
|
||||
if editor.toPlainText() == text:
|
||||
return
|
||||
self._loading = True
|
||||
editor.setPlainText(text)
|
||||
self._loading = False
|
||||
|
||||
def _text_changed(self) -> None:
|
||||
if self._loading or self._component is None:
|
||||
return
|
||||
|
||||
editor = self.sender()
|
||||
section = self._section_for_editor(editor)
|
||||
self.equation_text_change_requested.emit(self._component, section, editor.toPlainText().splitlines(), self._edit_id)
|
||||
self._edit_timer.start()
|
||||
|
||||
def eventFilter(self, watched: QObject, event: QEvent) -> bool:
|
||||
if watched in self._text_editors() and event.type() in (QEvent.Type.FocusIn, QEvent.Type.FocusOut):
|
||||
self.finish_text_edit()
|
||||
return super().eventFilter(watched, event)
|
||||
|
||||
def _text_editors(self) -> tuple:
|
||||
return (self.ui.declarationsTextEdit, self.ui.initialEquationsTextEdit, self.ui.equationsTextEdit)
|
||||
|
||||
def _section_for_editor(self, editor: QObject) -> str:
|
||||
if editor is self.ui.declarationsTextEdit:
|
||||
return "declarations"
|
||||
if editor is self.ui.initialEquationsTextEdit:
|
||||
return "initial_equations"
|
||||
return "equations"
|
||||
|
||||
def _editor_for_section(self, section: str):
|
||||
return {
|
||||
"declarations": self.ui.declarationsTextEdit,
|
||||
"initial_equations": self.ui.initialEquationsTextEdit,
|
||||
"equations": self.ui.equationsTextEdit,
|
||||
}[section]
|
||||
|
||||
def _params_changed(self) -> None:
|
||||
if self._loading or self._component is None:
|
||||
return
|
||||
self._component.parameters = self.param_editor.params()
|
||||
self.component_changed.emit(self._component)
|
||||
|
||||
def _ports_changed(self) -> None:
|
||||
if self._loading or self._component is None:
|
||||
return
|
||||
self._component.interface.ports = self.port_editor.ports()
|
||||
self.component_changed.emit(self._component)
|
||||
|
||||
def _replace_placeholder(self, placeholder: QWidget, editor: QWidget) -> None:
|
||||
self.ui.verticalLayout_2.replaceWidget(placeholder, editor)
|
||||
placeholder.hide()
|
||||
placeholder.deleteLater()
|
||||
|
||||
def _set_editors_enabled(self, enabled: bool) -> None:
|
||||
self.ui.declarationsTextEdit.setEnabled(enabled)
|
||||
self.ui.initialEquationsTextEdit.setEnabled(enabled)
|
||||
self.ui.equationsTextEdit.setEnabled(enabled)
|
||||
self.param_editor.setEnabled(enabled)
|
||||
self.port_editor.setEnabled(enabled)
|
||||
@@ -1,7 +1,8 @@
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import QMainWindow, QTabWidget
|
||||
from PySide6.QtWidgets import QMainWindow, QTabWidget, QVBoxLayout
|
||||
|
||||
from bedit_gui.ui.generated.ui_main_window import Ui_MainWindow
|
||||
from bedit_gui.views.equation_editor_widget import EquationEditorWidget
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
@@ -11,6 +12,12 @@ class MainWindow(QMainWindow):
|
||||
self.ui = Ui_MainWindow()
|
||||
self.ui.setupUi(self)
|
||||
|
||||
central_layout = QVBoxLayout(self.ui.centralwidget)
|
||||
central_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.equation_editor = EquationEditorWidget(self.ui.centralwidget)
|
||||
self.equation_editor.hide()
|
||||
central_layout.addWidget(self.equation_editor)
|
||||
|
||||
self.setTabPosition(Qt.AllDockWidgetAreas, QTabWidget.North)
|
||||
self.setCorner(Qt.Corner.BottomLeftCorner, Qt.DockWidgetArea.LeftDockWidgetArea)
|
||||
self.setCorner(Qt.Corner.BottomRightCorner, Qt.DockWidgetArea.RightDockWidgetArea)
|
||||
|
||||
Reference in New Issue
Block a user