Compare commits

...

3 Commits

Author SHA1 Message Date
e88c58f095 Library drag-in 2026-08-17 17:52:12 +02:00
b1f453a6df Library tree filled with items 2026-08-17 17:47:26 +02:00
8d37b2441a lib settings 2026-08-17 17:44:58 +02:00
25 changed files with 884 additions and 219 deletions

View File

@@ -60,7 +60,7 @@ Responsibilities:
- `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.
- `models.py`: GUI metadata persisted inside the core document, including icon, graph, and simulation databases.
- `bedit_core`: domain model and serialization. It must never import from `bedit_gui`.
Preferred direction:
@@ -136,10 +136,12 @@ Clipboard support is intentionally extensible:
- `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`.
`ClipboardHandler` is the base implementation for editor-specific routing. The document tree and graph editor have component handlers registered 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.
The graph handler copies/cuts/deletes selected component items and deletes selected connection items. Paste targets the displayed graph and places new components at the mouse position, or at the viewport center when the mouse is outside the canvas.
## Icon editor conventions
- Icons are GUI metadata stored in `document.metadata["icon_database"]`.
@@ -153,6 +155,23 @@ Text widgets use their native `copy()`, `cut()`, and `paste()` methods. Componen
- 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.
## Graph editor conventions
- Graph GUI metadata is stored in `document.metadata["graph_database"]`. `GraphDatabase`, `Graph`, `GraphConnection`, and `GraphComponentLabel` live in `bedit_gui.models` and serialize through `to_data()`/`from_data()`.
- Core graph topology and connections remain in `bedit_core.models`; positions, routed points, labels, and other presentation metadata belong in the GUI graph database.
- Component positions are absolute scene positions. Connection metadata stores the full point list, including endpoints; only interior points are draggable corner items.
- Component labels are visible by default, italic, and centered below the rendered icon. Their persisted position is relative to the icons bottom-center. Label visibility and completed label moves are undoable.
- The graph editor has normal and connection modes. The toolbar actions are exclusive and Space toggles modes while focus is inside the editor.
- Normal mode supports component and label dragging. Connection mode shows icon ports, prevents component/label movement, and uses two component clicks to choose a compatible port pair.
- Signal connections require output-to-input. Signal outputs may fan out; signal inputs accept only one connection. A bond port accepts another connection only when its `multiplicity` is true. Bond domains must be compatible.
- While choosing a connection, the first component is highlighted and a temporary dashed line follows the mouse. When several port pairs are possible, output-to-input choices are listed first.
- Signal connections render with full arrows. Bond connections render with half arrows and a perpendicular causality tick. Connection endpoints are clipped to rendered icon bounds plus `CONNECTION_BOUNDING_BOX_SPACING`.
- Components, connections, and connection points are separate graphics items. Connections are selectable/deletable; connection points are draggable and have their own delete context action.
- Persistent canvas edits go through `Document` methods and graph-specific `QUndoCommand` classes. Incremental document signals must also update the editors cached `Graph`; otherwise rebuilding items during a mode switch can restore stale metadata.
- `render_icon(..., render_ports=True)` is the single port-rendering path. Do not duplicate icon or port geometry in the graph editor.
- Graph sizing and rendering constants, including `COMPONENT_LABEL_FONT_SIZE`, live near the top of `views/graph_editor_widget.py`.
- Double-clicking a canvas component selects its document-tree row and opens its graph or equation editor. Canvas component context menus share the document-tree editing actions and add graph-only presentation actions such as Show Label.
## Actions and shortcut routing
- Put visible actions in Designer menus/toolbars.
@@ -161,6 +180,12 @@ Text widgets use their native `copy()`, `cut()`, and `paste()` methods. Componen
- Scope destructive shortcuts to the relevant widget where appropriate.
- Always guard the operation itself even when an action is disabled for presentation.
## Application settings
- `ApplicationSettings` is the typed `QSettings` facade for BEdit preferences. The current settings include log level, graph snap-to-grid size, and ordered library paths under `libraries/paths`.
- The Settings dialog edits library paths locally until OK is accepted. It supports BEdit `.bedit.json`/`.json` and `.beb` files, directories, duplicate suppression, extended selection, and list-focused Delete-key removal.
- Add settings behavior in the handwritten dialog/controller/service modules, never in generated UI Python.
## Simulator application
- `bedit_gui/simulation_application.py` is the composition root for the separate `bedit-sim` Qt application.
@@ -173,6 +198,14 @@ Text widgets use their native `copy()`, `cut()`, and `paste()` methods. Componen
- BEdit launches the simulator as a separate process. Compile/Open Simulation integration may transfer a `.bes` file to that process.
- Keep simulator file workflows in their own services/controllers rather than adding them to `MainWindow` or the editor document controller.
- Keep compiled-executable launching, time-window progression, result loading, and cancellation inside `bedit_simulation`. GUI controllers may schedule backend calls and present state, but must not execute or manage simulation binaries themselves.
- The BEdit Compile action performs bond-graph causality inference before Modelica compilation. Inference runs on a deep copy first, then inferred `causality`/`undesired` state is applied to the open document with an undoable command.
- OpenModelica compilation runs `checkModel(...)` before `buildModel(...)`; the equation/variable summary flows through `ModelBuildResult.output` and is shown in the application log.
### Causality inference caveats
- `_CausalityEngine.inference()` clears every flattened bonds causality to `NONE` before inference; it does not continue from saved causalities. It currently does not clear old `undesired` flags.
- Preferred causalities are assigned before all junction constraints are resolved, and the engine has no backtracking to relax a preferred assignment. Some soft-preference conflicts therefore raise a junction error instead of marking a bond undesired.
- `propagate_to_neighbor()` currently selects `connection.target` in both branches. When propagation starts at a target component, it should traverse to the source; account for this known bug when diagnosing inference behavior.
## Verification

View File

@@ -1,25 +0,0 @@
{
"shapes": {
"2a5bf670-c821-442b-9d46-1b07faa70722": {
"layer": 0,
"type": "text",
"pos": [
-32,
-32
],
"width": 64,
"height": 64,
"color": "#000000ff",
"bold": true,
"italic": false,
"size": 64.0,
"text": "0"
}
},
"port_positions": {
"497b1f74-1186-471f-976a-36b07a451caf": [
-8,
-8
]
}
}

View File

@@ -1,25 +0,0 @@
{
"shapes": {
"2a5bf670-c821-442b-9d46-1b07faa70722": {
"layer": 0,
"type": "text",
"pos": [
-32,
-32
],
"width": 64,
"height": 64,
"color": "#000000ff",
"bold": true,
"italic": false,
"size": 64.0,
"text": "1"
}
},
"port_positions": {
"4306701a-6b1b-4d19-b8ff-45dbfa04f2d3": [
-8,
-8
]
}
}

View File

@@ -1,29 +0,0 @@
{
"shapes": {
"2a5bf670-c821-442b-9d46-1b07faa70722": {
"layer": 0,
"type": "text",
"pos": [
-32,
-32
],
"width": 64.0,
"height": 64.0,
"color": "#000000ff",
"bold": true,
"italic": false,
"size": 64.0,
"text": "C"
}
},
"port_positions": {
"c87881f1-23b4-4a69-b586-8e3c7bf6e21e": [
-8,
-8
],
"c62de8eb-e13b-4849-bea5-c8cc5332269e": [
16,
-32
]
}
}

View File

@@ -1,29 +0,0 @@
{
"shapes": {
"2a5bf670-c821-442b-9d46-1b07faa70722": {
"layer": 0,
"type": "text",
"pos": [
-32,
-32
],
"width": 64,
"height": 64,
"color": "#000000ff",
"bold": true,
"italic": false,
"size": 64.0,
"text": "I"
}
},
"port_positions": {
"0b9036b1-e4e4-437e-8c35-f5bb391b6cbd": [
-8,
-8
],
"fb25f35d-4c0c-4cfa-92d4-1a18a71e2c11": [
16,
-32
]
}
}

View File

@@ -1,29 +0,0 @@
{
"shapes": {
"2a5bf670-c821-442b-9d46-1b07faa70722": {
"layer": 0,
"type": "text",
"pos": [
-32,
-32
],
"width": 64,
"height": 64,
"color": "#000000ff",
"bold": true,
"italic": false,
"size": 64.0,
"text": "R"
}
},
"port_positions": {
"0ac7d4ea-77f7-4c0d-8e37-406ef66e4740": [
-8,
-8
],
"d1f5aab2-7bff-4b1e-8697-98fb6c3d8f02": [
16,
-32
]
}
}

View File

@@ -1,25 +0,0 @@
{
"shapes": {
"2a5bf670-c821-442b-9d46-1b07faa70722": {
"layer": 0,
"type": "text",
"pos": [
-48,
-32
],
"width": 96.0,
"height": 64.0,
"color": "#000000ff",
"bold": true,
"italic": false,
"size": 64.0,
"text": "Se"
}
},
"port_positions": {
"5b6a8a0c-0875-402c-bd74-11d086aac372": [
-8,
-8
]
}
}

View File

@@ -1,25 +0,0 @@
{
"shapes": {
"f1a38ee1-8a53-454d-a398-12c14032ba79": {
"layer": 0,
"type": "text",
"pos": [
-48,
-32
],
"width": 96.0,
"height": 64.0,
"color": "#000000ff",
"bold": true,
"italic": false,
"size": 64.0,
"text": "Sf"
}
},
"port_positions": {
"856b4152-e18f-46d7-889d-626cb98474aa": [
-8,
-8
]
}
}

View File

@@ -8,6 +8,7 @@ from PySide6.QtWidgets import QApplication, QMessageBox
from bedit_gui.controllers.clipboard_controller import ClipboardController, DocumentTreeClipboardHandler, GraphEditorClipboardHandler, TextClipboardHandler
from bedit_gui.controllers.document_controller import DocumentController
from bedit_gui.controllers.log_controller import LogController
from bedit_gui.controllers.library_controller import LibraryController
from bedit_gui.controllers.settings_controller import SettingsController
from bedit_gui.controllers.simulation_settings_controller import SimulationSettingsController
from bedit_gui.controllers.simulation_controller import SimulationController
@@ -52,12 +53,14 @@ def main() -> int:
LogController(window, settings.log_level)
DocumentController(document, window)
SettingsController(window, settings)
settings_controller = SettingsController(window, settings)
SimulationSettingsController(document, window)
SimulationController(document, window)
UndoController(document, window)
ViewMenuController(window)
document_tree_controller = DocumentTreeController(document, window)
library_controller = LibraryController(window, settings)
settings_controller.library_paths_changed.connect(library_controller.reload)
clipboard = ClipboardService(app)
ClipboardController(window, clipboard, [TextClipboardHandler(clipboard), DocumentTreeClipboardHandler(document, window.ui.documentTree, document_tree_controller.model, clipboard), GraphEditorClipboardHandler(document, window.graph_editor, clipboard)])

View File

@@ -182,6 +182,7 @@ class GraphEditorClipboardHandler(ClipboardHandler):
self.editor = editor
self.clipboard = clipboard
editor.scene.selectionChanged.connect(self.availability_changed)
editor.component_drop_requested.connect(self.drop_components)
def owns_focus(self, widget: QWidget) -> bool:
return widget is self.editor or self.editor.isAncestorOf(widget)
@@ -214,12 +215,19 @@ class GraphEditorClipboardHandler(ClipboardHandler):
payload = self.clipboard.get_json(ClipboardService.COMPONENTS_MIME)
if graph_component is None or not isinstance(graph_component.implementation, GraphImplementation) or payload is None:
return
x, y = self.editor.paste_position()
self._paste_payload(graph_component, payload, (x, y))
def drop_components(self, graph_component: Component, payload: dict, position: tuple[int, int]) -> None:
self._paste_payload(graph_component, payload, position)
def _paste_payload(self, graph_component: Component, payload: dict, position: tuple[int, int]) -> None:
try:
components, icons = import_components(payload)
except (TypeError, ValueError) as exc:
QMessageBox.critical(self.editor, "Could not paste components", str(exc))
return
x, y = self.editor.paste_position()
x, y = position
spacing = self.editor.snap_to_grid_size * 4
positions = {component_id: (x + index * spacing, y + index * spacing) for index, component_id in enumerate(components)}
self.document.paste_graph_components(graph_component, components, icons, positions)

View File

@@ -0,0 +1,76 @@
from PySide6.QtCore import QObject, QSize, Qt
from PySide6.QtWidgets import QAbstractItemView, QHeaderView
from bedit_core.models import Component, ComponentID, GraphImplementation
from bedit_gui.models import Icon, IconDatabase
from bedit_gui.services.application_settings import ApplicationSettings
from bedit_gui.services.component_clipboard import export_component_data
from bedit_gui.services.libraries import load_library_documents
from bedit_gui.utils.icon import render_fitted_icon
from bedit_gui.views.main_window import MainWindow
from bedit_gui.views.models.library_tree_model import LibraryTreeModel
ICON_SIZE = QSize(32, 32)
class LibraryController(QObject):
def __init__(self, window: MainWindow, settings: ApplicationSettings) -> None:
super().__init__(window)
self.window = window
self.settings = settings
self._component_sources: dict[int, tuple[ComponentID, dict[ComponentID, Icon]]] = {}
self.model = LibraryTreeModel(self._component_payload)
tree = window.ui.libraryTree
tree.setModel(self.model)
tree.setHeaderHidden(True)
tree.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
tree.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
tree.setDragEnabled(True)
tree.setDragDropMode(QAbstractItemView.DragDropMode.DragOnly)
tree.setDefaultDropAction(Qt.DropAction.CopyAction)
tree.setIconSize(QSize(48, 48))
tree.header().setStretchLastSection(False)
tree.header().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
tree.header().setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
tree.setColumnWidth(1, 56)
self.reload()
def reload(self) -> None:
libraries = load_library_documents(self.settings.library_paths)
self._component_sources = {}
for library in libraries:
database = library.document.metadata.get("icon_database") if library.document.metadata is not None else None
icons = database.icons if isinstance(database, IconDatabase) else {}
self._collect_component_sources(library.document.root, icons)
self.model.set_documents([library.document for library in libraries])
for library in libraries:
database = library.document.metadata.get("icon_database") if library.document.metadata is not None else None
icons = database.icons if isinstance(database, IconDatabase) else {}
self._set_component_icons(library.document.root, icons)
self.window.ui.libraryTree.expandAll()
def _component_payload(self, components: list[Component]) -> dict:
roots = {}
icons = {}
for component in components:
source = self._component_sources.get(id(component))
if source is None:
continue
component_id, source_icons = source
roots[component_id] = component
icons.update(source_icons)
return export_component_data(roots, icons)
def _collect_component_sources(self, components: dict[ComponentID, Component], icons: dict[ComponentID, Icon]) -> None:
for component_id, component in components.items():
self._component_sources[id(component)] = (component_id, icons)
if isinstance(component.implementation, GraphImplementation):
self._collect_component_sources(component.implementation.graph.components, icons)
def _set_component_icons(self, components: dict[ComponentID, Component], icons: dict[ComponentID, Icon]) -> None:
for component_id, component in components.items():
self.model.set_component_icon(component_id, render_fitted_icon(icons.get(component_id, Icon()), component.interface.ports, ICON_SIZE))
if isinstance(component.implementation, GraphImplementation):
self._set_component_icons(component.implementation.graph.components, icons)

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
from collections.abc import Callable
from typing import Protocol
from PySide6.QtCore import QObject
from PySide6.QtCore import QObject, Signal
from PySide6.QtWidgets import QDialog
from bedit_gui.services.application_logging import get_logger, set_log_level
@@ -21,15 +21,20 @@ class SettingsDialogLike(Protocol):
@property
def snap_to_grid_size(self) -> int: ...
@property
def library_paths(self) -> list[str]: ...
def exec(self) -> int: ...
SettingsDialogFactory = Callable[[int, int, MainWindow], SettingsDialogLike]
SettingsDialogFactory = Callable[[int, int, list[str], MainWindow], SettingsDialogLike]
class SettingsController(QObject):
"""Opens the settings dialog and applies accepted preferences."""
library_paths_changed = Signal()
def __init__(
self,
window: MainWindow,
@@ -44,12 +49,16 @@ class SettingsController(QObject):
window.ui.actionSettings.triggered.connect(self.open_settings)
def open_settings(self) -> None:
dialog = self.dialog_factory(self.settings.log_level, self.settings.snap_to_grid_size, self.window)
dialog = self.dialog_factory(self.settings.log_level, self.settings.snap_to_grid_size, self.settings.library_paths, self.window)
if dialog.exec() != QDialog.DialogCode.Accepted:
return
old_library_paths = self.settings.library_paths
self.settings.log_level = dialog.log_level
self.settings.snap_to_grid_size = dialog.snap_to_grid_size
self.settings.library_paths = dialog.library_paths
self.window.graph_editor.set_snap_to_grid_size(dialog.snap_to_grid_size)
set_log_level(dialog.log_level)
if self.settings.library_paths != old_library_paths:
self.library_paths_changed.emit()
logger.info("Application settings updated")

View File

@@ -12,6 +12,7 @@ class ApplicationSettings:
DEFAULT_LOG_LEVEL = logging.INFO
SNAP_TO_GRID_SIZE_KEY = "graph/snap_to_grid_size"
DEFAULT_SNAP_TO_GRID_SIZE = 4
LIBRARY_PATHS_KEY = "libraries/paths"
def __init__(self, settings: QSettings | None = None) -> None:
self._settings = settings if settings is not None else QSettings()
@@ -38,6 +39,17 @@ class ApplicationSettings:
raise ValueError("snap-to-grid size must be positive")
self._settings.setValue(self.SNAP_TO_GRID_SIZE_KEY, size)
@property
def library_paths(self) -> list[str]:
value = self._settings.value(self.LIBRARY_PATHS_KEY, [])
if isinstance(value, str):
return [value]
return [str(path) for path in value] if isinstance(value, (list, tuple)) else []
@library_paths.setter
def library_paths(self, paths: list[str]) -> None:
self._settings.setValue(self.LIBRARY_PATHS_KEY, list(dict.fromkeys(paths)))
class SimulationApplicationSettings:
"""Typed access to persistent BEsim application settings."""

View File

@@ -6,9 +6,11 @@ from typing import Any
from PySide6.QtCore import QMimeData, QObject, Signal
from PySide6.QtGui import QClipboard, QGuiApplication
from bedit_gui.services.component_clipboard import COMPONENTS_MIME
class ClipboardService(QObject):
COMPONENTS_MIME = "application/x-bedit-components+json"
COMPONENTS_MIME = COMPONENTS_MIME
changed = Signal()
def __init__(self, parent: QObject | None = None) -> None:

View File

@@ -9,18 +9,25 @@ from bedit_gui.documents import Document as GuiDocument
from bedit_gui.models import Icon, ShapeID
FORMAT_VERSION = 1
COMPONENTS_MIME = "application/x-bedit-components+json"
def export_components(document: GuiDocument, components: list[Component]) -> dict[str, Any]:
roots = {document.component_id(component): component for component in components}
serialized = document_to_data(Document(format_version=1, id=ID(), name="Clipboard", root=roots))
component_ids = _all_component_ids(roots)
icons = {}
for component_id in component_ids:
icon = document.stored_component_icon(component_id)
if icon is not None:
icons[str(component_id)] = icon.to_data()
return {"format_version": FORMAT_VERSION, "type": "components", "components": serialized["root"], "icons": icons}
icons[component_id] = icon
return export_component_data(roots, icons)
def export_component_data(components: dict[ComponentID, Component], icons: dict[ComponentID, Icon]) -> dict[str, Any]:
serialized = document_to_data(Document(format_version=1, id=ID(), name="Clipboard", root=components))
component_ids = set(_all_component_ids(components))
icon_data = {str(component_id): icon.to_data() for component_id, icon in icons.items() if component_id in component_ids}
return {"format_version": FORMAT_VERSION, "type": "components", "components": serialized["root"], "icons": icon_data}
def import_components(payload: dict[str, Any]) -> tuple[dict[ComponentID, Component], dict[ComponentID, Icon]]:

View File

@@ -0,0 +1,42 @@
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from bedit_core.models import Document
from bedit_gui.services import document_files
from bedit_gui.services.application_logging import get_logger
logger = get_logger(__name__)
@dataclass(frozen=True)
class LoadedLibrary:
path: Path
document: Document
def list_library_files(library_paths: list[str]) -> list[Path]:
files = []
seen = set()
for configured_path in library_paths:
path = Path(configured_path).expanduser()
candidates = [path] if path.is_file() else sorted(path.rglob("*"), key=lambda candidate: str(candidate).casefold()) if path.is_dir() else []
for candidate in candidates:
if not candidate.is_file() or candidate.suffix.lower() not in (".beb", ".json"):
continue
resolved = candidate.resolve()
if resolved not in seen:
seen.add(resolved)
files.append(resolved)
return files
def load_library_documents(library_paths: list[str]) -> list[LoadedLibrary]:
libraries = []
for path in list_library_files(library_paths):
try:
libraries.append(LoadedLibrary(path, document_files.load(path)))
except (KeyError, OSError, TypeError, ValueError) as exc:
logger.warning("Could not load library %s: %s", path, exc)
return libraries

View File

@@ -30,7 +30,7 @@
<x>0</x>
<y>0</y>
<width>940</width>
<height>22</height>
<height>19</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
@@ -127,7 +127,7 @@
<property name="minimumSize">
<size>
<width>150</width>
<height>533</height>
<height>200</height>
</size>
</property>
<property name="windowTitle">
@@ -180,6 +180,27 @@
<addaction name="actionCompile_Model"/>
<addaction name="actionOpen_Simulation_Window"/>
</widget>
<widget class="QDockWidget" name="libraryWidget">
<property name="minimumSize">
<size>
<width>150</width>
<height>200</height>
</size>
</property>
<property name="windowTitle">
<string>Libraries</string>
</property>
<attribute name="dockWidgetArea">
<number>1</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents_4">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QTreeView" name="libraryTree"/>
</item>
</layout>
</widget>
</widget>
<action name="actionOpen_File">
<property name="icon">
<iconset resource="../../resources/resources.qrc">

View File

@@ -0,0 +1,362 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'main_window.ui'
##
## Created by: Qt User Interface Compiler version 6.11.1
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
QCursor, QFont, QFontDatabase, QGradient,
QIcon, QImage, QKeySequence, QLinearGradient,
QPainter, QPalette, QPixmap, QRadialGradient,
QTransform)
from PySide6.QtWidgets import (QApplication, QDockWidget, QHeaderView, QListView,
QMainWindow, QMenu, QMenuBar, QSizePolicy,
QStatusBar, QTabWidget, QToolBar, QTreeView,
QVBoxLayout, QWidget)
import resources_rc
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
if not MainWindow.objectName():
MainWindow.setObjectName(u"MainWindow")
MainWindow.resize(940, 729)
icon = QIcon()
icon.addFile(u":/icons/icons/office-chart-line.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
MainWindow.setWindowIcon(icon)
MainWindow.setDocumentMode(False)
MainWindow.setTabShape(QTabWidget.TabShape.Triangular)
self.actionOpen_File = QAction(MainWindow)
self.actionOpen_File.setObjectName(u"actionOpen_File")
icon1 = QIcon()
icon1.addFile(u":/icons/icons/document-open.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionOpen_File.setIcon(icon1)
self.actionOpen_File.setMenuRole(QAction.MenuRole.NoRole)
self.actionNew_File = QAction(MainWindow)
self.actionNew_File.setObjectName(u"actionNew_File")
icon2 = QIcon()
icon2.addFile(u":/icons/icons/document-new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionNew_File.setIcon(icon2)
self.actionNew_File.setMenuRole(QAction.MenuRole.NoRole)
self.actionSave_File = QAction(MainWindow)
self.actionSave_File.setObjectName(u"actionSave_File")
icon3 = QIcon()
icon3.addFile(u":/icons/icons/document-save.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionSave_File.setIcon(icon3)
self.actionSave_File.setMenuRole(QAction.MenuRole.NoRole)
self.actionSave_File_As = QAction(MainWindow)
self.actionSave_File_As.setObjectName(u"actionSave_File_As")
icon4 = QIcon()
icon4.addFile(u":/icons/icons/document-save-as.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionSave_File_As.setIcon(icon4)
self.actionSave_File_As.setMenuRole(QAction.MenuRole.NoRole)
self.actionClose = QAction(MainWindow)
self.actionClose.setObjectName(u"actionClose")
self.actionClose.setMenuRole(QAction.MenuRole.NoRole)
self.actionAbout_QT = QAction(MainWindow)
self.actionAbout_QT.setObjectName(u"actionAbout_QT")
self.actionAbout_QT.setMenuRole(QAction.MenuRole.AboutQtRole)
self.actionUndo = QAction(MainWindow)
self.actionUndo.setObjectName(u"actionUndo")
icon5 = QIcon()
icon5.addFile(u":/icons/icons/edit-undo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionUndo.setIcon(icon5)
self.actionUndo.setMenuRole(QAction.MenuRole.NoRole)
self.actionRedo = QAction(MainWindow)
self.actionRedo.setObjectName(u"actionRedo")
icon6 = QIcon()
icon6.addFile(u":/icons/icons/edit-redo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionRedo.setIcon(icon6)
self.actionRedo.setMenuRole(QAction.MenuRole.NoRole)
self.actionReset_Layout = QAction(MainWindow)
self.actionReset_Layout.setObjectName(u"actionReset_Layout")
self.actionReset_Layout.setMenuRole(QAction.MenuRole.NoRole)
self.actionPanels = QAction(MainWindow)
self.actionPanels.setObjectName(u"actionPanels")
self.actionToolbars = QAction(MainWindow)
self.actionToolbars.setObjectName(u"actionToolbars")
self.actionSettings = QAction(MainWindow)
self.actionSettings.setObjectName(u"actionSettings")
self.actionDelete = QAction(MainWindow)
self.actionDelete.setObjectName(u"actionDelete")
icon7 = QIcon()
icon7.addFile(u":/icons/icons/edit-delete.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionDelete.setIcon(icon7)
self.actionDelete.setMenuRole(QAction.MenuRole.NoRole)
self.actionEscape = QAction(MainWindow)
self.actionEscape.setObjectName(u"actionEscape")
self.actionEscape.setMenuRole(QAction.MenuRole.NoRole)
self.actionCopy = QAction(MainWindow)
self.actionCopy.setObjectName(u"actionCopy")
icon8 = QIcon()
icon8.addFile(u":/icons/icons/edit-copy.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionCopy.setIcon(icon8)
self.actionCopy.setMenuRole(QAction.MenuRole.NoRole)
self.actionPaste = QAction(MainWindow)
self.actionPaste.setObjectName(u"actionPaste")
icon9 = QIcon()
icon9.addFile(u":/icons/icons/edit-paste.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionPaste.setIcon(icon9)
self.actionPaste.setMenuRole(QAction.MenuRole.NoRole)
self.actionCut = QAction(MainWindow)
self.actionCut.setObjectName(u"actionCut")
icon10 = QIcon()
icon10.addFile(u":/icons/icons/edit-cut.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionCut.setIcon(icon10)
self.actionCut.setMenuRole(QAction.MenuRole.NoRole)
self.actionSimulation_Settings = QAction(MainWindow)
self.actionSimulation_Settings.setObjectName(u"actionSimulation_Settings")
icon11 = QIcon()
icon11.addFile(u":/icons/icons/configure.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionSimulation_Settings.setIcon(icon11)
self.actionSimulation_Settings.setMenuRole(QAction.MenuRole.NoRole)
self.actionEdit_Parameters = QAction(MainWindow)
self.actionEdit_Parameters.setObjectName(u"actionEdit_Parameters")
icon12 = QIcon()
icon12.addFile(u":/icons/icons/view-form-table.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionEdit_Parameters.setIcon(icon12)
self.actionEdit_Parameters.setMenuRole(QAction.MenuRole.NoRole)
self.actionCompile_Model = QAction(MainWindow)
self.actionCompile_Model.setObjectName(u"actionCompile_Model")
icon13 = QIcon()
icon13.addFile(u":/icons/icons/run-build.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionCompile_Model.setIcon(icon13)
self.actionCompile_Model.setMenuRole(QAction.MenuRole.NoRole)
self.actionOpen_Simulation_Window = QAction(MainWindow)
self.actionOpen_Simulation_Window.setObjectName(u"actionOpen_Simulation_Window")
self.actionOpen_Simulation_Window.setIcon(icon)
self.actionOpen_Simulation_Window.setMenuRole(QAction.MenuRole.NoRole)
self.actionAbout = QAction(MainWindow)
self.actionAbout.setObjectName(u"actionAbout")
self.centralwidget = QWidget(MainWindow)
self.centralwidget.setObjectName(u"centralwidget")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QMenuBar(MainWindow)
self.menubar.setObjectName(u"menubar")
self.menubar.setGeometry(QRect(0, 0, 940, 19))
self.menuFile = QMenu(self.menubar)
self.menuFile.setObjectName(u"menuFile")
self.menuEdit = QMenu(self.menubar)
self.menuEdit.setObjectName(u"menuEdit")
self.menuView = QMenu(self.menubar)
self.menuView.setObjectName(u"menuView")
self.menuHelp = QMenu(self.menubar)
self.menuHelp.setObjectName(u"menuHelp")
self.menuSimulation = QMenu(self.menubar)
self.menuSimulation.setObjectName(u"menuSimulation")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QStatusBar(MainWindow)
self.statusbar.setObjectName(u"statusbar")
MainWindow.setStatusBar(self.statusbar)
self.fileToolBar = QToolBar(MainWindow)
self.fileToolBar.setObjectName(u"fileToolBar")
MainWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.fileToolBar)
self.undoToolBar = QToolBar(MainWindow)
self.undoToolBar.setObjectName(u"undoToolBar")
MainWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.undoToolBar)
self.documentTreeWidget = QDockWidget(MainWindow)
self.documentTreeWidget.setObjectName(u"documentTreeWidget")
self.documentTreeWidget.setMinimumSize(QSize(150, 200))
self.dockWidgetContents = QWidget()
self.dockWidgetContents.setObjectName(u"dockWidgetContents")
self.verticalLayout = QVBoxLayout(self.dockWidgetContents)
self.verticalLayout.setObjectName(u"verticalLayout")
self.documentTree = QTreeView(self.dockWidgetContents)
self.documentTree.setObjectName(u"documentTree")
self.verticalLayout.addWidget(self.documentTree)
self.documentTreeWidget.setWidget(self.dockWidgetContents)
MainWindow.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, self.documentTreeWidget)
self.logWidget = QDockWidget(MainWindow)
self.logWidget.setObjectName(u"logWidget")
self.logWidget.setMinimumSize(QSize(150, 107))
self.dockWidgetContents_5 = QWidget()
self.dockWidgetContents_5.setObjectName(u"dockWidgetContents_5")
self.verticalLayout_2 = QVBoxLayout(self.dockWidgetContents_5)
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
self.listView = QListView(self.dockWidgetContents_5)
self.listView.setObjectName(u"listView")
self.verticalLayout_2.addWidget(self.listView)
self.logWidget.setWidget(self.dockWidgetContents_5)
MainWindow.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, self.logWidget)
self.simToolBar = QToolBar(MainWindow)
self.simToolBar.setObjectName(u"simToolBar")
MainWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.simToolBar)
self.libraryWidget = QDockWidget(MainWindow)
self.libraryWidget.setObjectName(u"libraryWidget")
self.libraryWidget.setMinimumSize(QSize(150, 200))
self.dockWidgetContents_4 = QWidget()
self.dockWidgetContents_4.setObjectName(u"dockWidgetContents_4")
self.verticalLayout_4 = QVBoxLayout(self.dockWidgetContents_4)
self.verticalLayout_4.setObjectName(u"verticalLayout_4")
self.libraryTree = QTreeView(self.dockWidgetContents_4)
self.libraryTree.setObjectName(u"libraryTree")
self.verticalLayout_4.addWidget(self.libraryTree)
self.libraryWidget.setWidget(self.dockWidgetContents_4)
MainWindow.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, self.libraryWidget)
self.menubar.addAction(self.menuFile.menuAction())
self.menubar.addAction(self.menuEdit.menuAction())
self.menubar.addAction(self.menuView.menuAction())
self.menubar.addAction(self.menuSimulation.menuAction())
self.menubar.addAction(self.menuHelp.menuAction())
self.menuFile.addAction(self.actionNew_File)
self.menuFile.addAction(self.actionOpen_File)
self.menuFile.addSeparator()
self.menuFile.addAction(self.actionSave_File)
self.menuFile.addAction(self.actionSave_File_As)
self.menuFile.addSeparator()
self.menuFile.addAction(self.actionClose)
self.menuEdit.addAction(self.actionUndo)
self.menuEdit.addAction(self.actionRedo)
self.menuEdit.addSeparator()
self.menuEdit.addAction(self.actionCopy)
self.menuEdit.addAction(self.actionCut)
self.menuEdit.addAction(self.actionPaste)
self.menuEdit.addSeparator()
self.menuEdit.addAction(self.actionDelete)
self.menuEdit.addSeparator()
self.menuEdit.addAction(self.actionSettings)
self.menuView.addAction(self.actionReset_Layout)
self.menuView.addAction(self.actionPanels)
self.menuView.addAction(self.actionToolbars)
self.menuHelp.addAction(self.actionAbout)
self.menuHelp.addAction(self.actionAbout_QT)
self.menuSimulation.addAction(self.actionSimulation_Settings)
self.menuSimulation.addAction(self.actionEdit_Parameters)
self.menuSimulation.addSeparator()
self.menuSimulation.addAction(self.actionCompile_Model)
self.menuSimulation.addAction(self.actionOpen_Simulation_Window)
self.fileToolBar.addAction(self.actionNew_File)
self.fileToolBar.addAction(self.actionOpen_File)
self.fileToolBar.addAction(self.actionSave_File)
self.fileToolBar.addAction(self.actionSave_File_As)
self.undoToolBar.addAction(self.actionUndo)
self.undoToolBar.addAction(self.actionRedo)
self.undoToolBar.addAction(self.actionCopy)
self.undoToolBar.addAction(self.actionCut)
self.undoToolBar.addAction(self.actionPaste)
self.simToolBar.addAction(self.actionSimulation_Settings)
self.simToolBar.addAction(self.actionEdit_Parameters)
self.simToolBar.addAction(self.actionCompile_Model)
self.simToolBar.addAction(self.actionOpen_Simulation_Window)
self.retranslateUi(MainWindow)
QMetaObject.connectSlotsByName(MainWindow)
# setupUi
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None))
self.actionOpen_File.setText(QCoreApplication.translate("MainWindow", u"Open File", None))
#if QT_CONFIG(tooltip)
self.actionOpen_File.setToolTip(QCoreApplication.translate("MainWindow", u"Open a file", None))
#endif // QT_CONFIG(tooltip)
#if QT_CONFIG(shortcut)
self.actionOpen_File.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+O", None))
#endif // QT_CONFIG(shortcut)
self.actionNew_File.setText(QCoreApplication.translate("MainWindow", u"New File", None))
#if QT_CONFIG(tooltip)
self.actionNew_File.setToolTip(QCoreApplication.translate("MainWindow", u"Create a new file", None))
#endif // QT_CONFIG(tooltip)
#if QT_CONFIG(shortcut)
self.actionNew_File.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+N", None))
#endif // QT_CONFIG(shortcut)
self.actionSave_File.setText(QCoreApplication.translate("MainWindow", u"Save File", None))
#if QT_CONFIG(tooltip)
self.actionSave_File.setToolTip(QCoreApplication.translate("MainWindow", u"Save a file to disk", None))
#endif // QT_CONFIG(tooltip)
#if QT_CONFIG(shortcut)
self.actionSave_File.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+S", None))
#endif // QT_CONFIG(shortcut)
self.actionSave_File_As.setText(QCoreApplication.translate("MainWindow", u"Save File As...", None))
#if QT_CONFIG(tooltip)
self.actionSave_File_As.setToolTip(QCoreApplication.translate("MainWindow", u"Save file to disk as another file", None))
#endif // QT_CONFIG(tooltip)
#if QT_CONFIG(shortcut)
self.actionSave_File_As.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+Shift+S", None))
#endif // QT_CONFIG(shortcut)
self.actionClose.setText(QCoreApplication.translate("MainWindow", u"Close", None))
#if QT_CONFIG(tooltip)
self.actionClose.setToolTip(QCoreApplication.translate("MainWindow", u"Close application", None))
#endif // QT_CONFIG(tooltip)
#if QT_CONFIG(shortcut)
self.actionClose.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+Q", None))
#endif // QT_CONFIG(shortcut)
self.actionAbout_QT.setText(QCoreApplication.translate("MainWindow", u"About QT", None))
self.actionUndo.setText(QCoreApplication.translate("MainWindow", u"Undo", None))
#if QT_CONFIG(shortcut)
self.actionUndo.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+Z", None))
#endif // QT_CONFIG(shortcut)
self.actionRedo.setText(QCoreApplication.translate("MainWindow", u"Redo", None))
#if QT_CONFIG(shortcut)
self.actionRedo.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+Y", None))
#endif // QT_CONFIG(shortcut)
self.actionReset_Layout.setText(QCoreApplication.translate("MainWindow", u"Reset Layout", None))
#if QT_CONFIG(tooltip)
self.actionReset_Layout.setToolTip(QCoreApplication.translate("MainWindow", u"Reset window layout to default", None))
#endif // QT_CONFIG(tooltip)
self.actionPanels.setText(QCoreApplication.translate("MainWindow", u"Panels", None))
self.actionToolbars.setText(QCoreApplication.translate("MainWindow", u"Toolbars", None))
self.actionSettings.setText(QCoreApplication.translate("MainWindow", u"Settings", None))
self.actionDelete.setText(QCoreApplication.translate("MainWindow", u"Delete", None))
#if QT_CONFIG(tooltip)
self.actionDelete.setToolTip(QCoreApplication.translate("MainWindow", u"Delete selected", None))
#endif // QT_CONFIG(tooltip)
#if QT_CONFIG(shortcut)
self.actionDelete.setShortcut(QCoreApplication.translate("MainWindow", u"Del", None))
#endif // QT_CONFIG(shortcut)
self.actionEscape.setText(QCoreApplication.translate("MainWindow", u"Escape", None))
#if QT_CONFIG(shortcut)
self.actionEscape.setShortcut(QCoreApplication.translate("MainWindow", u"Esc", None))
#endif // QT_CONFIG(shortcut)
self.actionCopy.setText(QCoreApplication.translate("MainWindow", u"Copy", None))
#if QT_CONFIG(tooltip)
self.actionCopy.setToolTip(QCoreApplication.translate("MainWindow", u"Copy selected", None))
#endif // QT_CONFIG(tooltip)
#if QT_CONFIG(shortcut)
self.actionCopy.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+C", None))
#endif // QT_CONFIG(shortcut)
self.actionPaste.setText(QCoreApplication.translate("MainWindow", u"Paste", None))
#if QT_CONFIG(tooltip)
self.actionPaste.setToolTip(QCoreApplication.translate("MainWindow", u"Paste selected", None))
#endif // QT_CONFIG(tooltip)
#if QT_CONFIG(shortcut)
self.actionPaste.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+V", None))
#endif // QT_CONFIG(shortcut)
self.actionCut.setText(QCoreApplication.translate("MainWindow", u"Cut", None))
#if QT_CONFIG(tooltip)
self.actionCut.setToolTip(QCoreApplication.translate("MainWindow", u"Cut selected", None))
#endif // QT_CONFIG(tooltip)
#if QT_CONFIG(shortcut)
self.actionCut.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+X", None))
#endif // QT_CONFIG(shortcut)
self.actionSimulation_Settings.setText(QCoreApplication.translate("MainWindow", u"Simulation Settings", None))
self.actionEdit_Parameters.setText(QCoreApplication.translate("MainWindow", u"Edit Parameters", None))
self.actionCompile_Model.setText(QCoreApplication.translate("MainWindow", u"Compile Model", None))
self.actionOpen_Simulation_Window.setText(QCoreApplication.translate("MainWindow", u"Open Simulation Window", None))
self.actionAbout.setText(QCoreApplication.translate("MainWindow", u"About", None))
self.menuFile.setTitle(QCoreApplication.translate("MainWindow", u"File", None))
self.menuEdit.setTitle(QCoreApplication.translate("MainWindow", u"Edit", None))
self.menuView.setTitle(QCoreApplication.translate("MainWindow", u"View", None))
self.menuHelp.setTitle(QCoreApplication.translate("MainWindow", u"Help", None))
self.menuSimulation.setTitle(QCoreApplication.translate("MainWindow", u"Simulation", None))
self.fileToolBar.setWindowTitle(QCoreApplication.translate("MainWindow", u"toolBar", None))
self.undoToolBar.setWindowTitle(QCoreApplication.translate("MainWindow", u"toolBar", None))
self.documentTreeWidget.setWindowTitle(QCoreApplication.translate("MainWindow", u"Document Tree", None))
self.logWidget.setWindowTitle(QCoreApplication.translate("MainWindow", u"Log", None))
self.simToolBar.setWindowTitle(QCoreApplication.translate("MainWindow", u"toolBar", None))
self.libraryWidget.setWindowTitle(QCoreApplication.translate("MainWindow", u"Libraries", None))
# retranslateUi

View File

@@ -17,7 +17,7 @@
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
<number>1</number>
</property>
<widget class="QWidget" name="General">
<attribute name="title">
@@ -90,6 +90,34 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="Libraries">
<attribute name="title">
<string>Libraries</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QListView" name="listView"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="addLibButton">
<property name="text">
<string>Add library</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="addDirButton">
<property name="text">
<string>Add directory</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
<item>

View File

@@ -0,0 +1,128 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'settings_dialog.ui'
##
## Created by: Qt User Interface Compiler version 6.11.1
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QAbstractButton, QApplication, QComboBox, QDialog,
QDialogButtonBox, QFormLayout, QHBoxLayout, QLabel,
QListView, QPushButton, QSizePolicy, QSpacerItem,
QSpinBox, QTabWidget, QVBoxLayout, QWidget)
class Ui_Settings(object):
def setupUi(self, Settings):
if not Settings.objectName():
Settings.setObjectName(u"Settings")
Settings.resize(400, 230)
self.verticalLayout = QVBoxLayout(Settings)
self.verticalLayout.setObjectName(u"verticalLayout")
self.tabWidget = QTabWidget(Settings)
self.tabWidget.setObjectName(u"tabWidget")
self.General = QWidget()
self.General.setObjectName(u"General")
self.formLayout = QFormLayout(self.General)
self.formLayout.setObjectName(u"formLayout")
self.logLevel = QComboBox(self.General)
self.logLevel.addItem("")
self.logLevel.addItem("")
self.logLevel.addItem("")
self.logLevel.addItem("")
self.logLevel.setObjectName(u"logLevel")
self.formLayout.setWidget(0, QFormLayout.ItemRole.FieldRole, self.logLevel)
self.lableLogLevel = QLabel(self.General)
self.lableLogLevel.setObjectName(u"lableLogLevel")
self.formLayout.setWidget(0, QFormLayout.ItemRole.LabelRole, self.lableLogLevel)
self.labelSnapToGridSize = QLabel(self.General)
self.labelSnapToGridSize.setObjectName(u"labelSnapToGridSize")
self.formLayout.setWidget(1, QFormLayout.ItemRole.LabelRole, self.labelSnapToGridSize)
self.snapToGridSize = QSpinBox(self.General)
self.snapToGridSize.setObjectName(u"snapToGridSize")
self.snapToGridSize.setMinimum(1)
self.snapToGridSize.setMaximum(256)
self.snapToGridSize.setValue(4)
self.formLayout.setWidget(1, QFormLayout.ItemRole.FieldRole, self.snapToGridSize)
self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
self.formLayout.setItem(2, QFormLayout.ItemRole.FieldRole, self.verticalSpacer)
self.tabWidget.addTab(self.General, "")
self.Libraries = QWidget()
self.Libraries.setObjectName(u"Libraries")
self.verticalLayout_2 = QVBoxLayout(self.Libraries)
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
self.listView = QListView(self.Libraries)
self.listView.setObjectName(u"listView")
self.verticalLayout_2.addWidget(self.listView)
self.horizontalLayout = QHBoxLayout()
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.addLibButton = QPushButton(self.Libraries)
self.addLibButton.setObjectName(u"addLibButton")
self.horizontalLayout.addWidget(self.addLibButton)
self.addDirButton = QPushButton(self.Libraries)
self.addDirButton.setObjectName(u"addDirButton")
self.horizontalLayout.addWidget(self.addDirButton)
self.verticalLayout_2.addLayout(self.horizontalLayout)
self.tabWidget.addTab(self.Libraries, "")
self.verticalLayout.addWidget(self.tabWidget)
self.buttonBox = QDialogButtonBox(Settings)
self.buttonBox.setObjectName(u"buttonBox")
self.buttonBox.setOrientation(Qt.Orientation.Horizontal)
self.buttonBox.setStandardButtons(QDialogButtonBox.StandardButton.Cancel|QDialogButtonBox.StandardButton.Ok)
self.verticalLayout.addWidget(self.buttonBox)
self.retranslateUi(Settings)
self.buttonBox.accepted.connect(Settings.accept)
self.buttonBox.rejected.connect(Settings.reject)
self.tabWidget.setCurrentIndex(1)
QMetaObject.connectSlotsByName(Settings)
# setupUi
def retranslateUi(self, Settings):
Settings.setWindowTitle(QCoreApplication.translate("Settings", u"Settings", None))
self.logLevel.setItemText(0, QCoreApplication.translate("Settings", u"Debug", None))
self.logLevel.setItemText(1, QCoreApplication.translate("Settings", u"Info", None))
self.logLevel.setItemText(2, QCoreApplication.translate("Settings", u"Warning", None))
self.logLevel.setItemText(3, QCoreApplication.translate("Settings", u"Error", None))
self.lableLogLevel.setText(QCoreApplication.translate("Settings", u"Log level:", None))
self.labelSnapToGridSize.setText(QCoreApplication.translate("Settings", u"Snap-to-grid size:", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.General), QCoreApplication.translate("Settings", u"General", None))
self.addLibButton.setText(QCoreApplication.translate("Settings", u"Add library", None))
self.addDirButton.setText(QCoreApplication.translate("Settings", u"Add directory", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.Libraries), QCoreApplication.translate("Settings", u"Libraries", None))
# retranslateUi

View File

@@ -1,8 +1,11 @@
from __future__ import annotations
import logging
from pathlib import Path
from PySide6.QtWidgets import QDialog, QWidget
from PySide6.QtCore import QStringListModel, Qt
from PySide6.QtGui import QKeySequence, QShortcut
from PySide6.QtWidgets import QAbstractItemView, QDialog, QFileDialog, QWidget
from bedit_gui.ui.generated.ui_settings_dialog import Ui_Settings
@@ -21,6 +24,7 @@ class SettingsDialog(QDialog):
self,
log_level: int,
snap_to_grid_size: int,
library_paths: list[str],
parent: QWidget | None = None,
) -> None:
super().__init__(parent)
@@ -37,6 +41,14 @@ class SettingsDialog(QDialog):
selected if selected >= 0 else self.LOG_LEVELS.index(logging.INFO)
)
self.ui.snapToGridSize.setValue(snap_to_grid_size)
self._library_paths = QStringListModel(list(library_paths), self)
self.ui.listView.setModel(self._library_paths)
self.ui.listView.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
self.ui.addLibButton.clicked.connect(self._add_library_file)
self.ui.addDirButton.clicked.connect(self._add_library_directory)
self._delete_shortcut = QShortcut(QKeySequence.StandardKey.Delete, self.ui.listView)
self._delete_shortcut.setContext(Qt.ShortcutContext.WidgetShortcut)
self._delete_shortcut.activated.connect(self._delete_selected_paths)
@property
def log_level(self) -> int:
@@ -45,3 +57,29 @@ class SettingsDialog(QDialog):
@property
def snap_to_grid_size(self) -> int:
return self.ui.snapToGridSize.value()
@property
def library_paths(self) -> list[str]:
return self._library_paths.stringList()
def _add_library_file(self) -> None:
path, _selected_filter = QFileDialog.getOpenFileName(self, "Add BEdit Library", "", "BEdit documents (*.bedit.json *.beb *.json)")
if path:
self._add_library_path(path)
def _add_library_directory(self) -> None:
path = QFileDialog.getExistingDirectory(self, "Add Library Directory")
if path:
self._add_library_path(path)
def _add_library_path(self, path: str) -> None:
normalized = str(Path(path).resolve())
paths = self._library_paths.stringList()
if normalized not in paths:
paths.append(normalized)
self._library_paths.setStringList(paths)
def _delete_selected_paths(self) -> None:
rows = sorted((index.row() for index in self.ui.listView.selectionModel().selectedRows()), reverse=True)
for row in rows:
self._library_paths.removeRow(row)

View File

@@ -1,15 +1,17 @@
from __future__ import annotations
import json
from enum import Enum
from itertools import pairwise
from math import hypot
from PySide6.QtCore import QEvent, QObject, QPointF, QRectF, QSize, QTimer, Qt, Signal
from PySide6.QtGui import QActionGroup, QBrush, QColor, QCursor, QKeySequence, QMouseEvent, QPainter, QPainterPath, QPen, QShortcut, QWheelEvent
from PySide6.QtGui import QActionGroup, QBrush, QColor, QCursor, QDragEnterEvent, QDragMoveEvent, QDropEvent, QKeySequence, QMouseEvent, QPainter, QPainterPath, QPen, QShortcut, QWheelEvent
from PySide6.QtWidgets import QGraphicsEllipseItem, QGraphicsItem, QGraphicsPathItem, QGraphicsPixmapItem, QGraphicsScene, QGraphicsSceneMouseEvent, QGraphicsTextItem, QGraphicsView, QMenu, QWidget
from bedit_core.models import BondCausality, BondConnection, BondPort, Component, ComponentID, Connection, ConnectionID, GraphImplementation, Port, PortID, SignalConnection, SignalDirection, SignalPort
from bedit_gui.models import Graph, GraphComponentLabel, GraphConnection, Icon
from bedit_gui.services.component_clipboard import COMPONENTS_MIME
from bedit_gui.ui.generated.ui_graph_editor_widget import Ui_graphEditorWidget
from bedit_gui.utils.icon import get_pixmap_bounding_box, render_icon
@@ -291,6 +293,7 @@ class GraphEditorWidget(QWidget):
connection_points_change_requested = Signal(object, object, object, str)
connection_add_requested = Signal(object, object)
connections_delete_requested = Signal(object, object)
component_drop_requested = Signal(object, object, object)
def __init__(self, parent: QWidget | None = None, snap_to_grid_size: int = 4) -> None:
super().__init__(parent)
@@ -314,6 +317,7 @@ class GraphEditorWidget(QWidget):
self.ui.graphicsView.setScene(self.scene)
self.ui.graphicsView.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse)
self.ui.graphicsView.viewport().installEventFilter(self)
self.ui.graphicsView.viewport().setAcceptDrops(True)
self._mode_actions = QActionGroup(self)
self._mode_actions.setExclusive(True)
self._mode_actions.addAction(self.ui.actionMouseMode)
@@ -736,6 +740,25 @@ class GraphEditorWidget(QWidget):
self.ui.graphicsView.centerOn(bounds.center())
def eventFilter(self, watched: QObject, event: QEvent) -> bool:
if watched is self.ui.graphicsView.viewport() and event.type() in (QEvent.Type.DragEnter, QEvent.Type.DragMove):
assert isinstance(event, (QDragEnterEvent, QDragMoveEvent))
if self._component is not None and event.mimeData().hasFormat(COMPONENTS_MIME):
event.setDropAction(Qt.DropAction.CopyAction)
event.accept()
return True
if watched is self.ui.graphicsView.viewport() and event.type() == QEvent.Type.Drop:
assert isinstance(event, QDropEvent)
if self._component is not None and event.mimeData().hasFormat(COMPONENTS_MIME):
try:
payload = json.loads(bytes(event.mimeData().data(COMPONENTS_MIME)).decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError):
return True
if isinstance(payload, dict):
position = self._snap_position(self.ui.graphicsView.mapToScene(event.position().toPoint()))
self.component_drop_requested.emit(self._component, payload, position)
event.setDropAction(Qt.DropAction.CopyAction)
event.accept()
return True
if watched is self.ui.graphicsView.viewport() and event.type() == QEvent.Type.MouseMove:
assert isinstance(event, QMouseEvent)
self._update_connection_preview(self.ui.graphicsView.mapToScene(event.position().toPoint()))

View File

@@ -22,19 +22,21 @@ class DocumentTreeModel(QAbstractItemModel):
rename_document_requested = Signal(str)
rename_component_requested = Signal(Component, str)
def __init__(self) -> None:
def __init__(self, editable: bool = True) -> None:
super().__init__()
self._document: CoreDocument | None = None
self._editable = editable
self._root = DocumentTreeNode("Document", None, None, None, [])
self._component_icons: dict[ComponentID, QIcon] = {}
self._component_nodes: dict[ComponentID, DocumentTreeNode] = {}
def set_document(self, document: CoreDocument) -> None:
self.set_documents([document])
def set_documents(self, documents: list[CoreDocument]) -> None:
self.beginResetModel()
self._document = document
self._component_icons = {}
self._component_nodes = {}
self._root = self._build_tree(document)
self._root = self._build_tree(documents)
self.endResetModel()
def set_component_icon(self, component_id: ComponentID, icon: QIcon) -> None:
@@ -146,12 +148,12 @@ class DocumentTreeModel(QAbstractItemModel):
node = index.internalPointer()
# Make the document root node editable
if index.column() == 0 and isinstance(node, DocumentTreeNode) and isinstance(node.value, (CoreDocument, Component)):
if self._editable and index.column() == 0 and isinstance(node, DocumentTreeNode) and isinstance(node.value, (CoreDocument, Component)):
flags |= Qt.ItemFlag.ItemIsEditable
return flags
def _build_tree(self, document: CoreDocument) -> DocumentTreeNode:
def _build_tree(self, documents: list[CoreDocument]) -> DocumentTreeNode:
# QT's invisible root
root = DocumentTreeNode(
name="",
@@ -160,19 +162,18 @@ class DocumentTreeModel(QAbstractItemModel):
parent=None,
children=[],
)
# Add itself as a child so the document root is visible in the tree
document_root = DocumentTreeNode(name=document.name, value=document, component_id=None, parent=root, children=[])
root.children.append(document_root)
def _list_children(root: DocumentTreeNode, components: dict[ComponentID, Component]) -> None:
def _list_children(parent: DocumentTreeNode, components: dict[ComponentID, Component]) -> None:
for component_id, component in sorted(components.items(), key=lambda item: (item[1].name.casefold(), item[1].name, str(item[0]))):
component_node = DocumentTreeNode(name=component.name, value=component, component_id=component_id, parent=root, children=[])
root.children.append(component_node)
component_node = DocumentTreeNode(name=component.name, value=component, component_id=component_id, parent=parent, children=[])
parent.children.append(component_node)
self._component_nodes[component_id] = component_node
if isinstance(component.implementation, GraphImplementation):
_list_children(component_node, component.implementation.graph.components)
_list_children(document_root, document.root)
for document in documents:
document_root = DocumentTreeNode(name=document.name, value=document, component_id=None, parent=root, children=[])
root.children.append(document_root)
_list_children(document_root, document.root)
return root

View File

@@ -0,0 +1,55 @@
from __future__ import annotations
import json
from collections.abc import Callable
from PySide6.QtCore import QMimeData, QModelIndex, Qt
from bedit_core.models import Component
from bedit_gui.services.component_clipboard import COMPONENTS_MIME
from bedit_gui.views.models.document_tree_model import DocumentTreeModel
PayloadFactory = Callable[[list[Component]], dict]
class LibraryTreeModel(DocumentTreeModel):
def __init__(self, payload_factory: PayloadFactory) -> None:
super().__init__(editable=False)
self._payload_factory = payload_factory
def flags(self, index: QModelIndex) -> Qt.ItemFlag:
flags = super().flags(index)
if isinstance(self.value(index), Component):
flags |= Qt.ItemFlag.ItemIsDragEnabled
return flags
def mimeTypes(self) -> list[str]:
return [COMPONENTS_MIME]
def mimeData(self, indexes: list[QModelIndex]) -> QMimeData:
rows = [index for index in indexes if index.column() == 0 and isinstance(self.value(index), Component)]
selected = {id(self.value(index)) for index in rows}
components = []
added = set()
for index in rows:
parent = index.parent()
if any(id(self.value(parent_index)) in selected for parent_index in self._parents(parent)):
continue
component = self.value(index)
if isinstance(component, Component) and id(component) not in added:
components.append(component)
added.add(id(component))
mime = QMimeData()
if components:
mime.setData(COMPONENTS_MIME, json.dumps(self._payload_factory(components)).encode("utf-8"))
mime.setText("\n".join(component.name for component in components))
return mime
def supportedDragActions(self) -> Qt.DropAction:
return Qt.DropAction.CopyAction
@staticmethod
def _parents(index: QModelIndex):
while index.isValid():
yield index
index = index.parent()

View File

@@ -374,14 +374,14 @@
"connection_type": "bond",
"source": "4306701a-6b1b-4d19-b8ff-45dbfa04f2d3",
"target": "497b1f74-1186-471f-976a-36b07a451caf",
"causality": "effort_out",
"causality": "flow_out",
"undesired": false
},
"743e35a4-e736-407d-bf34-b4afbcde5a0b": {
"connection_type": "bond",
"source": "4306701a-6b1b-4d19-b8ff-45dbfa04f2d3",
"target": "0ac7d4ea-77f7-4c0d-8e37-406ef66e4740",
"causality": "flow_out",
"causality": "effort_out",
"undesired": false
},
"68727b23-888b-45b4-a0f4-801ac7b39601": {
@@ -626,6 +626,10 @@
"1455acad-931b-4caa-be71-aff4283507c6": [
64,
384
],
"015a0697-69a9-43bf-9410-d2c8b44303f8": [
64,
384
]
},
"component_labels": {