diff --git a/src/bedit_gui/controllers/document_tree_controller.py b/src/bedit_gui/controllers/document_tree_controller.py
index 7f46ea2..0af36db 100644
--- a/src/bedit_gui/controllers/document_tree_controller.py
+++ b/src/bedit_gui/controllers/document_tree_controller.py
@@ -2,7 +2,8 @@ from collections.abc import Callable
from functools import partial
from typing import Protocol
-from PySide6.QtCore import QObject, QPoint, QSize, Qt
+from PySide6.QtCore import QEvent, QObject, QPoint, QSize, Qt
+from PySide6.QtGui import QMouseEvent
from PySide6.QtWidgets import QAbstractItemView, QDialog, QHeaderView, QMenu
from bedit_core.models import Component, ComponentID, EquationImplementation, GraphImplementation, Port, PortID, Parameter, ParameterID
@@ -79,15 +80,20 @@ class DocumentTreeController(QObject):
window.ui.documentTree.header().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
window.ui.documentTree.header().setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
window.ui.documentTree.setColumnWidth(1, 56)
- window.ui.documentTree.setContextMenuPolicy(
- Qt.ContextMenuPolicy.CustomContextMenu
- )
- window.ui.documentTree.customContextMenuRequested.connect(
- self._show_context_menu
- )
+ self._tree_viewport = window.ui.documentTree.viewport()
+ self._tree_viewport.installEventFilter(self)
self._on_document_changed(document.model)
+ def eventFilter(self, watched: QObject, event: QEvent) -> bool:
+ if watched is self._tree_viewport and event.type() in (QEvent.Type.MouseButtonPress, QEvent.Type.MouseButtonRelease):
+ assert isinstance(event, QMouseEvent)
+ if event.button() == Qt.MouseButton.RightButton:
+ if event.type() == QEvent.Type.MouseButtonRelease:
+ self._show_context_menu(event.position().toPoint())
+ return True
+ return super().eventFilter(watched, event)
+
def _on_document_changed(self, model: CoreDocument) -> None:
"""Rebuild the tree whenever New/Open replaces the core document."""
self._show_equation_component(None)
diff --git a/src/bedit_gui/models.py b/src/bedit_gui/models.py
index 2e4e610..eac6e22 100644
--- a/src/bedit_gui/models.py
+++ b/src/bedit_gui/models.py
@@ -1,4 +1,3 @@
-"""GUI document data stored inside the core document metadata field."""
from __future__ import annotations
from collections.abc import Mapping
@@ -12,6 +11,9 @@ from bedit_core.models import ComponentID, ID, PortID
class ShapeID(ID):
pass
+class SimulationID(ID):
+ pass
+
@dataclass
class Shape:
layer: int
@@ -32,7 +34,6 @@ class Shape:
def to_data(self) -> dict[str, Any]:
return {"layer": self.layer, "type": self.type, "pos": list(self.pos)}
-
class LineType(Enum):
NONE = "none"
SOLID = "solid"
@@ -40,7 +41,6 @@ class LineType(Enum):
DOTTED = "dotted"
DASH_DOT = "dash_dot"
-
@dataclass
class Line(Shape):
type: str = field(init=False, default="line")
@@ -58,7 +58,6 @@ class Line(Shape):
def to_data(self) -> dict[str, Any]:
return {**super().to_data(), "end": list(self.end), "line_type": self.line_type.value, "line_thickness": self.line_thickness, "line_color": self.line_color}
-
@dataclass
class Rectangle(Shape):
type: str = field(init=False, default="rectangle")
@@ -111,7 +110,6 @@ class Icon:
def to_data(self) -> dict[str, Any]:
return {"shapes": {str(key): shape.to_data() for key, shape in self.shapes.items()}, "port_positions": {str(key): list(position) for key, position in self.port_positions.items()}}
-
@dataclass
class IconDatabase:
format_version: int = 1
@@ -124,3 +122,61 @@ class IconDatabase:
def to_data(self) -> dict[str, Any]:
return {"format_version": self.format_version, "icons": {str(key): icon.to_data() for key, icon in self.icons.items()}}
+
+class SimulationMethod(Enum):
+ DASSL = "dassl"
+
+@dataclass
+class Simulation:
+ component: ComponentID
+ name: str
+
+ start_time: float
+ duration: float
+ use_timed_steps: bool
+ number_of_steps: int
+ step_size: float
+
+ method: SimulationMethod
+
+ dassl_tolerance: float
+
+ @classmethod
+ def from_data(cls, data: Mapping[str, Any]) -> Simulation:
+ return cls(
+ component=SimulationID(data.get("component"), SimulationID()),
+ name=data.get("name", ""),
+ start_time=float(data.get("start_time", "0.0")),
+ duration=float(data.get("duration", "1.0")),
+ use_timed_steps=bool(data.get("use_timed_steps", "false")),
+ number_of_steps=int(data.get("number_of_steps", "500")),
+ step_size=float(data.get("step_size", "0.001")),
+ method=SimulationMethod(data.get("method", "dassl")),
+ dassl_tolerance=float(data.get("dassl_tolerance", "1e-6")),
+ )
+ def to_data(self) -> dict[str, Any]:
+ return {
+ "component": str(self.component),
+ "name": self.name,
+ "start_time": str(self.start_time),
+ "duration": str(self.duration),
+ "use_timed_steps": str(self.use_timed_steps),
+ "number_of_steps": str(self.number_of_steps),
+ "step_size": str(self.step_size),
+ "method": str(self.method),
+ "dassl_tolerance": str(self.dassl_tolerance),
+ }
+
+@dataclass
+class SimulationDatabase:
+ format_versio: int = 1
+ simulations: dict[SimulationID, Simulation] = field(default_factory=dict)
+
+ @classmethod
+ def from_data(cls, data: Mapping[str, Any]) -> SimulationDatabase:
+ sims = {SimulationID(key): Simulation.from_data(value) for key, value in data.get("simulations", {}).items()}
+ return cls(format_version=int(data.get("format_version", 1)), simulations=sims)
+
+ def to_data(self) -> dict[str, Any]:
+ return {"format_version": self.format_version, "simulations": {str(key): sim.to_data() for key, sim in self.simulations.items()}}
+
\ No newline at end of file
diff --git a/src/bedit_gui/ui/forms/main_window.ui b/src/bedit_gui/ui/forms/main_window.ui
index 6c1a0fb..533333d 100644
--- a/src/bedit_gui/ui/forms/main_window.ui
+++ b/src/bedit_gui/ui/forms/main_window.ui
@@ -74,9 +74,20 @@
+
+
@@ -153,6 +164,21 @@
+
+
+ toolBar
+
+
+ TopToolBarArea
+
+
+ false
+
+
+
+
+
+
@@ -386,6 +412,54 @@
QAction::MenuRole::NoRole
+
+
+
+ :/icons/icons/configure.png:/icons/icons/configure.png
+
+
+ Simulation Settings
+
+
+ QAction::MenuRole::NoRole
+
+
+
+
+
+ :/icons/icons/view-form-table.png:/icons/icons/view-form-table.png
+
+
+ Edit Parameters
+
+
+ QAction::MenuRole::NoRole
+
+
+
+
+
+ :/icons/icons/run-build.png:/icons/icons/run-build.png
+
+
+ Compile Model
+
+
+ QAction::MenuRole::NoRole
+
+
+
+
+
+ :/icons/icons/office-chart-line.png:/icons/icons/office-chart-line.png
+
+
+ Open Simulation Window
+
+
+ QAction::MenuRole::NoRole
+
+
diff --git a/src/bedit_gui/ui/forms/simulation_settings.ui b/src/bedit_gui/ui/forms/simulation_settings.ui
new file mode 100644
index 0000000..f460840
--- /dev/null
+++ b/src/bedit_gui/ui/forms/simulation_settings.ui
@@ -0,0 +1,236 @@
+
+
+ Dialog
+
+
+
+ 0
+ 0
+ 562
+ 451
+
+
+
+ Dialog
+
+
+ -
+
+
-
+
+
+ -
+
+
+ QFrame::Shape::StyledPanel
+
+
+ QFrame::Shadow::Raised
+
+
+
-
+
+
-
+
+
+ Name:
+
+
+
+ -
+
+
+ Component:
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ Qt::Orientation::Horizontal
+
+
+
+ -
+
+
-
+
+
+ Start time:
+
+
+
+ -
+
+
+ Simulation length:
+
+
+
+ -
+
+
+ s
+
+
+
+ -
+
+
+ s
+
+
+
+ -
+
+
-
+
+
+ Step size
+
+
+
+ -
+
+
+ Number of steps
+
+
+
+
+
+ -
+
+
+ Step size:
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+ s
+
+
+ 4
+
+
+ 0.001000000000000
+
+
+
+ -
+
+
+ -
+
+
+ Number of steps:
+
+
+
+ -
+
+
+ -
+
+
+ Simulation method:
+
+
+
+
+
+ -
+
+
+ Qt::Orientation::Horizontal
+
+
+
+ -
+
+
-
+
+
+ 1e-06
+
+
+
+ -
+
+
+ Tolerance:
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Orientation::Horizontal
+
+
+ QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ Dialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ Dialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+