Equation editor undo
This commit is contained in:
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]
|
||||
@@ -58,8 +58,10 @@ class DocumentTreeController(QObject):
|
||||
|
||||
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)
|
||||
@@ -113,6 +115,10 @@ class DocumentTreeController(QObject):
|
||||
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)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtCore import QEvent, QObject, QTimer, Qt, Signal
|
||||
from PySide6.QtWidgets import QWidget
|
||||
|
||||
from bedit_core.models import Component, EquationImplementation
|
||||
@@ -13,6 +13,7 @@ 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:
|
||||
@@ -22,6 +23,11 @@ class EquationEditorWidget(QWidget):
|
||||
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)
|
||||
@@ -30,9 +36,10 @@ class EquationEditorWidget(QWidget):
|
||||
self.ui.sidebarButton.clicked.connect(self.toggle_sidebar)
|
||||
self.set_sidebar_visible(True)
|
||||
|
||||
self.ui.declarationsTextEdit.textChanged.connect(self._text_changed)
|
||||
self.ui.initialEquationsTextEdit.textChanged.connect(self._text_changed)
|
||||
self.ui.equationsTextEdit.textChanged.connect(self._text_changed)
|
||||
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)
|
||||
|
||||
@@ -48,6 +55,10 @@ class EquationEditorWidget(QWidget):
|
||||
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))
|
||||
|
||||
@@ -81,16 +92,49 @@ class EquationEditorWidget(QWidget):
|
||||
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
|
||||
|
||||
implementation = self._component.implementation
|
||||
assert isinstance(implementation, EquationImplementation)
|
||||
implementation.declarations = self.ui.declarationsTextEdit.toPlainText().splitlines()
|
||||
implementation.initial_equations = self.ui.initialEquationsTextEdit.toPlainText().splitlines()
|
||||
implementation.equations = self.ui.equationsTextEdit.toPlainText().splitlines()
|
||||
self.component_changed.emit(self._component)
|
||||
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:
|
||||
|
||||
Reference in New Issue
Block a user