diff --git a/BEdit/.vscode/tasks.json b/BEdit/.vscode/tasks.json index 82b7b37..12e8f76 100644 --- a/BEdit/.vscode/tasks.json +++ b/BEdit/.vscode/tasks.json @@ -48,7 +48,7 @@ "args": [ "${workspaceFolder}/resources/resources.qrc", "-o", - "${workspaceFolder}/src/bedit/resources_rc.py" + "${workspaceFolder}/src/bedit/gui/generated/resources_rc.py" ], "options": { "cwd": "${workspaceFolder}" @@ -68,7 +68,7 @@ "--from-imports", "${workspaceFolder}/ui/main_window.ui", "-o", - "${workspaceFolder}/src/bedit/ui_main_window.py" + "${workspaceFolder}/src/bedit/gui/generated/ui_main_window.py" ], "options": { "cwd": "${workspaceFolder}" @@ -87,7 +87,7 @@ "--from-imports", "${workspaceFolder}/ui/settings_dialog.ui", "-o", - "${workspaceFolder}/src/bedit/ui_settings_dialog.py" + "${workspaceFolder}/src/bedit/gui/generated/ui_settings_dialog.py" ], "options": { "cwd": "${workspaceFolder}" @@ -106,7 +106,7 @@ "--from-imports", "${workspaceFolder}/ui/component_options_dialog.ui", "-o", - "${workspaceFolder}/src/bedit/ui_component_options_dialog.py" + "${workspaceFolder}/src/bedit/gui/generated/ui_component_options_dialog.py" ], "options": {"cwd": "${workspaceFolder}"}, "problemMatcher": [], diff --git a/BEdit/AGENTS.md b/BEdit/AGENTS.md new file mode 100644 index 0000000..0af9c71 --- /dev/null +++ b/BEdit/AGENTS.md @@ -0,0 +1,194 @@ +# AGENTS.md + +This file is the working guide for AI coding agents contributing to BEdit. +Its instructions apply to the entire repository. + +## Project purpose + +BEdit is a Python/PySide6 graphical editor for hierarchical block and bond-graph +models. A document contains reusable components, typed and oriented ports, +connections, nested graphs, and editable vector icons. + +The application is evolving quickly. Prefer small, coherent changes that improve +the architecture without introducing compatibility layers unless compatibility is +explicitly requested. + +## Architecture + +The main boundary is strict: + +```text +src/bedit/ +├── __main__.py +├── core/ # Pure Python; must not import PySide6 +│ ├── model.py # Document, component, graph, port, icon data +│ ├── port_types.py # Port type definitions and compatibility +│ ├── serializer.py # JSON persistence +│ └── libraries.py # Library file discovery and parsing +└── gui/ # All Qt-dependent code + ├── app.py # QApplication startup and palette + ├── main_window.py # Top-level UI orchestration + ├── preferences.py # Explicit disk-backed QSettings factory + ├── controllers/ # Document controller and undo commands + ├── dialogs/ # Dialog behavior + ├── graphics/ # Workspace, icon editor, vector rendering + ├── models/ # Qt tree models and repository adapters + └── generated/ # Generated UI/resource Python; never hand-edit +``` + +Dependency direction: + +- `bedit.core` may depend only on the Python standard library. +- `bedit.gui` may depend on `bedit.core` and PySide6. +- `bedit.core` must never import `bedit.gui` or PySide6. +- Data parsing and validation belong in `core`. +- Signals, widgets, painting, Qt models, undo integration, and settings UI belong + in `gui`. + +Keep the root of `src/bedit` minimal. New UI modules should go into the relevant +`gui` subpackage rather than the package root. + +## Important behavior and design decisions + +- Components may contain nested graph or text implementations. +- Ports retain stable IDs. Display names, types, orientation, positions, and icon + anchors may change without changing IDs. +- Port orientation is presented as one unified list in the UI, while the model + indexes inputs and outputs separately for connection semantics. +- Port types are registered in `core/port_types.py`. Only compatible types may be + connected. `signal` is currently the only type. +- Port removal or reorientation must be rejected when it would invalidate an + existing connection. +- Connections reference port IDs, never port names. +- Icon editing uses a fixed 128×128 coordinate space. +- The visible/selectable component hitbox is calculated from vector elements, + not from the complete 128×128 icon canvas. +- New component icons default to a centered 64×64 shape. +- Vector elements include rectangles, circles, ellipses, lines, triangles, and + text. Shape styling and geometry are stored in document JSON. +- Icon port anchors are stored in `Port.properties["iconPosition"]`. +- Workspace grid size, workspace snapping size, and icon grid size are separate + persisted settings. +- Settings use `gui.preferences.application_settings()` and are stored under the + explicit `BEdit/BEdit` identity. Do not create anonymous `QSettings()` objects. +- Avoid the QSettings group name `general`; Qt treats `General` specially in INI + files. Autosave keys live under `autosave/`. +- User-visible document edits should participate in undo/redo. + +## Qt Designer and generated files + +Editable Designer sources are in `ui/`. Resources are defined in +`resources/resources.qrc`. + +Never hand-edit files in `src/bedit/gui/generated/`. Modify the corresponding +`.ui` or `.qrc` source and regenerate instead. + +Use the configured VS Code task **Qt: Build Designer Files**, or run the relevant +commands directly: + +```bash +pyside6-rcc resources/resources.qrc \ + -o src/bedit/gui/generated/resources_rc.py + +pyside6-uic --from-imports ui/main_window.ui \ + -o src/bedit/gui/generated/ui_main_window.py + +pyside6-uic --from-imports ui/settings_dialog.ui \ + -o src/bedit/gui/generated/ui_settings_dialog.py + +pyside6-uic --from-imports ui/component_options_dialog.ui \ + -o src/bedit/gui/generated/ui_component_options_dialog.py +``` + +When adding a promoted/custom widget in Designer, its header must use the real +Python module path, for example `bedit.gui.graphics.workspace`. + +Some dialogs are currently assembled programmatically. Keep that code inside +`gui/dialogs`; do not put widget creation in `core`. + +## Editing conventions + +- Preserve stable document IDs and existing connections. +- Validate candidate document changes before pushing an undo command. +- Keep model serialization symmetrical: additions to `to_dict()` require matching + handling in `from_dict()` and cloning where relevant. +- Use descriptive domain names; avoid generic `utils.py` modules. +- Prefer focused classes and helpers over growing `main_window.py` further. +- Shared vector calculations that do not require Qt belong in `core`; Qt painter + code belongs in `gui/graphics`. +- Do not silently swallow malformed document data. Raise a useful `ValueError` in + `core`, then present it through the GUI layer. +- Preserve unrelated user changes. The worktree may already be dirty. +- Do not delete or overwrite library/document JSON unless the requested workflow + explicitly calls for it. + +## Document and library files + +- Documents use the `bedit-document` JSON format. +- `test.bedit.json` is a useful manually created example during development. +- Library documents use the same recursive document model. +- Library parsing belongs in `core/libraries.py`; Qt change notifications belong + in `gui/models/library_repository.py`. +- Package library data, if present, belongs under `src/bedit/data/libraries/`. + +## Validation + +There is not yet a complete automated test suite. For every change, run at least: + +```bash +python3 -m compileall -q src +git diff --check +``` + +Run imports with the source tree explicitly available: + +```bash +PYTHONPATH=src python3 -c "import bedit.core; import bedit.gui.app" +``` + +Verify the backend remains Qt-free after core changes: + +```bash +PYTHONPATH=src python3 - <<'PY' +import sys +import bedit.core +assert not any(name.startswith("PySide6") for name in sys.modules) +PY +``` + +For model changes, add a focused in-memory round-trip check using +`GraphDocument.to_dict()` and `GraphDocument.from_dict()`. For controller changes, +exercise undo and redo when applicable. + +GUI smoke tests may fail in headless environments because the system Qt platform +theme tries to access a display even with an offscreen platform. Do not claim an +interactive GUI test passed unless a display-capable environment was actually +used. Compilation, imports, and pure model/controller checks remain useful. + +If Ruff is installed, also run: + +```bash +ruff check src +``` + +## Running the application + +Install the package in editable mode or run it from the source tree: + +```bash +PYTHONPATH=src python3 -m bedit +``` + +The installed GUI entry point is `bedit.gui.app:main`. + +## Completion checklist + +Before handing off a change: + +1. Confirm the `core`/`gui` dependency boundary is intact. +2. Confirm generated files were not hand-edited. +3. Check serialization and cloning for model changes. +4. Check undo/redo for document mutations. +5. Run compilation and `git diff --check`. +6. Report any GUI behavior that could not be tested interactively. +7. Update README or this file if the architecture or workflow changed. diff --git a/BEdit/README.md b/BEdit/README.md index 7c09107..9bdd555 100644 --- a/BEdit/README.md +++ b/BEdit/README.md @@ -36,12 +36,18 @@ After installation, the `bedit` command also launches the application. . ├── pyproject.toml dependencies, package metadata, and `bedit` command ├── README.md -├── ui/main_window.ui editable Qt Designer source +├── ui/ editable Qt Designer sources └── src/bedit - ├── __main__.py supports `python -m bedit` - ├── app.py starts Qt and applies the forced light palette - ├── main_window.py behavior and signal connections - └── ui_main_window.py generated from the Designer file; do not hand-edit + ├── __main__.py minimal `python -m bedit` entry point + ├── core/ Qt-free domain model, port types, files, libraries + └── gui/ every PySide-dependent module + ├── app.py Qt startup and application palette + ├── main_window.py top-level UI orchestration + ├── controllers/ document controller and undo commands + ├── dialogs/ dialog behavior + ├── graphics/ graph workspace and vector icon editor + ├── models/ Qt tree models and repository adapters + └── generated/ generated UI/resources; do not hand-edit ``` ## Designing the UI further @@ -61,11 +67,12 @@ Save the form, close the running application if necessary, then regenerate its Python wrapper: ```bash -pyside6-uic ui/main_window.ui -o src/bedit/ui_main_window.py +pyside6-uic --from-imports ui/main_window.ui \ + -o src/bedit/gui/generated/ui_main_window.py ``` -Do not hand-edit the generated Python file; change the `.ui` file and regenerate -it. Add behavior and signal connections in `main_window.py`. Widget names from +Do not hand-edit files in `gui/generated`; change the `.ui` source and regenerate +it. Add behavior and signal connections in `gui/main_window.py`. Widget names from Designer are available there through `self.ui`, such as `self.ui.graphView`. In VS Code, the same commands are available through **Terminal → Run Task**: @@ -76,16 +83,13 @@ In VS Code, the same commands are available through **Terminal → Run Task**: - The separate resource and UI compilation tasks remain available when only one generated file needs rebuilding. -## A sensible next design pass +## Architecture rules -1. Sketch the main tasks and screens before choosing widgets. -2. Turn each major area into its own widget class in `src/bedit/widgets/`. -3. Use a `QStackedWidget` for page-like navigation, or `QDockWidget` for movable - tool panels in an editor-style application. -4. Use reusable `QAction` objects for menu commands and any future toolbars. -5. Keep file/data operations outside widget classes as the application grows. -6. Add icons through a Qt resource file (`.qrc`) so packaging is reliable. -7. Test on Windows regularly; fonts, scaling, and native dialogs vary by platform. +- `bedit.core` must remain importable without PySide6. +- `bedit.gui` may depend on `core`; `core` must never import `gui`. +- Designer output and resource output belong only in `gui/generated`. +- Domain serialization and library parsing stay in `core`; Qt signals, models, + undo integration, painting, and widgets stay in `gui`. ## Graph and library prototype @@ -170,10 +174,9 @@ lines, triangles, and text. Each element owns its geometry, fill, stroke, and line style; ports keep their icon anchor in `properties.iconPosition`. The port type registry controls which types may connect (currently `signal` only). The workspace grid size, its finer snapping size, and the icon grid size are -configured independently in Settings. The -recursive model is under `src/bedit/document/`, library loading -and the live Current Document tree are under `src/bedit/library/`, and graphics -are isolated under `src/bedit/workspace/`. +configured independently in Settings. The recursive model and file loading are +under `src/bedit/core/`; the live Qt trees and graphics are isolated under +`src/bedit/gui/models/` and `src/bedit/gui/graphics/`. ## Optional tools diff --git a/BEdit/data/libraries/test.bedit.json b/BEdit/data/libraries/test.bedit.json deleted file mode 100644 index a79b1f5..0000000 --- a/BEdit/data/libraries/test.bedit.json +++ /dev/null @@ -1,162 +0,0 @@ -{ - "format": "bedit-document", - "version": 1, - "metadata": { - "name": "Untitled" - }, - "roots": [ - { - "id": "5ee742db-b25c-4d86-b4ca-cc0e61c4002a", - "name": "New Graph Block 1", - "position": { - "x": 0.0, - "y": 0.0 - }, - "rotation": 0.0, - "interface": { - "inputs": [], - "outputs": [] - }, - "icon": { - "shape": "rectangle", - "fill": "#f4f4f4", - "border": "#303030", - "text": "Graph", - "size": { - "width": 128.0, - "height": 128.0 - }, - "elements": [ - { - "type": "rectangle", - "x": 1.0, - "y": 1.0, - "width": 126.0, - "height": 126.0, - "fill": "#f4f4f4", - "stroke": "#303030", - "lineWidth": 1.5, - "lineStyle": "solid", - "cornerRadius": 5.0 - }, - { - "type": "text", - "x": 8.0, - "y": 8.0, - "width": 112.0, - "height": 112.0, - "text": "Graph", - "color": "#202020", - "fontSize": 12.0 - } - ] - }, - "properties": {}, - "library": { - "showSubtree": true - }, - "implementation": { - "kind": "graph", - "graph": { - "blocks": [ - { - "id": "89944479-e73e-446a-8d58-ebf35ac4144b", - "name": "New Graph Block 1", - "position": { - "x": 0.0, - "y": -96.0 - }, - "rotation": 0.0, - "interface": { - "inputs": [ - { - "id": "port-e5cb81a5", - "name": "Port 1", - "position": { - "x": 0.0, - "y": 0.0 - }, - "properties": { - "iconPosition": { - "x": 32.0, - "y": 64.0 - } - }, - "type": "signal" - } - ], - "outputs": [ - { - "id": "port-eb0034af", - "name": "Port 2", - "position": { - "x": 0.0, - "y": 0.0 - }, - "properties": { - "iconPosition": { - "x": 96.0, - "y": 64.0 - } - }, - "type": "signal" - } - ] - }, - "icon": { - "shape": "rectangle", - "fill": "#f4f4f4", - "border": "#303030", - "text": "Graph", - "size": { - "width": 128.0, - "height": 128.0 - }, - "elements": [ - { - "type": "rectangle", - "x": 32.0, - "y": 32.0, - "width": 64.0, - "height": 64.0, - "fill": "#f4f4f4", - "stroke": "#303030", - "lineWidth": 1.5, - "lineStyle": "solid", - "cornerRadius": 5.0 - }, - { - "type": "text", - "x": 40.0, - "y": 40.0, - "width": 48.0, - "height": 48.0, - "text": "Graph", - "color": "#303030", - "fontSize": 12.0, - "lineStyle": "solid", - "lineWidth": 1.5, - "stroke": "#303030", - "fill": "#ffffff" - } - ] - }, - "properties": {}, - "library": { - "showSubtree": true - }, - "implementation": { - "kind": "graph", - "graph": { - "blocks": [], - "connections": [] - } - } - } - ], - "connections": [] - } - } - } - ] -} diff --git a/BEdit/pyproject.toml b/BEdit/pyproject.toml index dda8867..58f9e45 100644 --- a/BEdit/pyproject.toml +++ b/BEdit/pyproject.toml @@ -19,7 +19,7 @@ dev = [ ] [project.gui-scripts] -bedit = "bedit.app:main" +bedit = "bedit.gui.app:main" [tool.setuptools.packages.find] where = ["src"] diff --git a/BEdit/src/bedit/__main__.py b/BEdit/src/bedit/__main__.py index 5127970..e338300 100644 --- a/BEdit/src/bedit/__main__.py +++ b/BEdit/src/bedit/__main__.py @@ -1,6 +1,5 @@ -from bedit.app import main +from bedit.gui.app import main if __name__ == "__main__": raise SystemExit(main()) - diff --git a/BEdit/src/bedit/core/__init__.py b/BEdit/src/bedit/core/__init__.py new file mode 100644 index 0000000..4c8dc39 --- /dev/null +++ b/BEdit/src/bedit/core/__init__.py @@ -0,0 +1,5 @@ +"""Pure BEdit domain model and persistence; this package has no Qt dependency.""" + +from bedit.core.model import Component, Connection, Endpoint, Graph, GraphDocument, Icon, Port + +__all__ = ["Component", "Connection", "Endpoint", "Graph", "GraphDocument", "Icon", "Port"] diff --git a/BEdit/src/bedit/core/libraries.py b/BEdit/src/bedit/core/libraries.py new file mode 100644 index 0000000..8da3b21 --- /dev/null +++ b/BEdit/src/bedit/core/libraries.py @@ -0,0 +1,32 @@ +import json +from dataclasses import dataclass +from pathlib import Path + +from bedit.core.model import GraphDocument + + +@dataclass(frozen=True) +class LibraryDocument: + name: str + document: GraphDocument + source_path: str + + +def bundled_library_path() -> Path: + return Path(__file__).resolve().parents[1] / "data" / "libraries" / "example.json" + + +def default_library_paths() -> list[str]: + return [str(bundled_library_path())] + + +def load_library_file(path: Path) -> LibraryDocument: + with path.open(encoding="utf-8") as file: + data = json.load(file) + document = GraphDocument.from_dict(data) + name = str(document.metadata.get("name") or path.stem) + return LibraryDocument(name, document, str(path)) + + +def library_candidates(path: Path) -> list[Path]: + return sorted(path.glob("*.json")) if path.is_dir() else [path] diff --git a/BEdit/src/bedit/document/model.py b/BEdit/src/bedit/core/model.py similarity index 99% rename from BEdit/src/bedit/document/model.py rename to BEdit/src/bedit/core/model.py index 6a1d248..758c835 100644 --- a/BEdit/src/bedit/document/model.py +++ b/BEdit/src/bedit/core/model.py @@ -5,7 +5,7 @@ from dataclasses import dataclass, field from typing import Any from uuid import uuid4 -from bedit.document.port_types import PortTypeRegistry +from bedit.core.port_types import PortTypeRegistry @dataclass diff --git a/BEdit/src/bedit/document/port_types.py b/BEdit/src/bedit/core/port_types.py similarity index 100% rename from BEdit/src/bedit/document/port_types.py rename to BEdit/src/bedit/core/port_types.py diff --git a/BEdit/src/bedit/document/serializer.py b/BEdit/src/bedit/core/serializer.py similarity index 93% rename from BEdit/src/bedit/document/serializer.py rename to BEdit/src/bedit/core/serializer.py index b810360..e9e720a 100644 --- a/BEdit/src/bedit/document/serializer.py +++ b/BEdit/src/bedit/core/serializer.py @@ -1,7 +1,7 @@ import json from pathlib import Path -from bedit.document.model import GraphDocument +from bedit.core.model import GraphDocument class JsonDocumentSerializer: @@ -20,4 +20,3 @@ class JsonDocumentSerializer: json.dump(document.to_dict(), file, indent=2) file.write("\n") temporary_path.replace(path) - diff --git a/BEdit/src/bedit/data/libraries/example.json b/BEdit/src/bedit/data/libraries/example.json new file mode 100644 index 0000000..2b788b4 --- /dev/null +++ b/BEdit/src/bedit/data/libraries/example.json @@ -0,0 +1,212 @@ +{ + "format": "bedit-document", + "version": 1, + "metadata": { + "name": "Example" + }, + "roots": [ + { + "id": "bf714517-e7b2-4f4b-8b53-55642e3beb28", + "name": "A", + "position": { + "x": 0.0, + "y": 0.0 + }, + "rotation": 0.0, + "interface": { + "inputs": [ + { + "id": "port-5c5d4695", + "name": "Port 1", + "position": { + "x": 0.0, + "y": 0.0 + }, + "properties": { + "iconPosition": { + "x": 32.0, + "y": 64.0 + } + }, + "type": "signal" + } + ], + "outputs": [ + { + "id": "port-de109124", + "name": "Port 2", + "position": { + "x": 0.0, + "y": 0.0 + }, + "properties": { + "iconPosition": { + "x": 96.0, + "y": 64.0 + } + }, + "type": "signal" + } + ] + }, + "icon": { + "shape": "rectangle", + "fill": "#f4f4f4", + "border": "#303030", + "text": "Text", + "size": { + "width": 128.0, + "height": 128.0 + }, + "elements": [ + { + "type": "rectangle", + "x": 32.0, + "y": 32.0, + "width": 64.0, + "height": 64.0, + "fill": "#f4f4f4", + "stroke": "#303030", + "lineWidth": 1.5, + "lineStyle": "solid", + "cornerRadius": 5.0 + }, + { + "type": "text", + "x": 40.0, + "y": 40.0, + "width": 48.0, + "height": 48.0, + "text": "A", + "color": "#303030", + "fontSize": 12.0, + "lineStyle": "solid", + "lineWidth": 1.5, + "stroke": "#303030", + "fill": "#ffffff" + } + ] + }, + "properties": {}, + "library": { + "showSubtree": true + }, + "implementation": { + "kind": "text", + "source": { + "equations": [], + "parameters": {} + } + } + }, + { + "id": "73c9d0c0-293d-4a55-8b89-a1f095dfa75f", + "name": "B", + "position": { + "x": 0.0, + "y": 0.0 + }, + "rotation": 0.0, + "interface": { + "inputs": [ + { + "id": "port-4f732b3e", + "name": "Port 1", + "position": { + "x": -176.0, + "y": -144.0 + }, + "properties": { + "iconPosition": { + "x": 32.0, + "y": 48.0 + } + }, + "type": "signal" + }, + { + "id": "port-ef7c4218", + "name": "Port 2", + "position": { + "x": -176.0, + "y": -16.0 + }, + "properties": { + "iconPosition": { + "x": 32.0, + "y": 80.0 + } + }, + "type": "signal" + } + ], + "outputs": [ + { + "id": "port-b68679b2", + "name": "Port 3", + "position": { + "x": 128.0, + "y": -80.0 + }, + "properties": { + "iconPosition": { + "x": 96.0, + "y": 64.0 + } + }, + "type": "signal" + } + ] + }, + "icon": { + "shape": "rectangle", + "fill": "#f4f4f4", + "border": "#303030", + "text": "Graph", + "size": { + "width": 128.0, + "height": 128.0 + }, + "elements": [ + { + "type": "rectangle", + "x": 32.0, + "y": 32.0, + "width": 64.0, + "height": 64.0, + "fill": "#f4f4f4", + "stroke": "#303030", + "lineWidth": 1.5, + "lineStyle": "solid", + "cornerRadius": 5.0 + }, + { + "type": "text", + "x": 40.0, + "y": 40.0, + "width": 48.0, + "height": 48.0, + "text": "B", + "color": "#303030", + "fontSize": 12.0, + "lineStyle": "solid", + "lineWidth": 1.5, + "stroke": "#303030", + "fill": "#ffffff" + } + ] + }, + "properties": {}, + "library": { + "showSubtree": true + }, + "implementation": { + "kind": "graph", + "graph": { + "blocks": [], + "connections": [] + } + } + } + ] +} diff --git a/BEdit/src/bedit/document/__init__.py b/BEdit/src/bedit/document/__init__.py deleted file mode 100644 index 33fe6a5..0000000 --- a/BEdit/src/bedit/document/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from bedit.document.controller import DocumentController -from bedit.document.model import Component, Connection, Endpoint, Graph, GraphDocument, Icon, Port - -__all__ = [ - "Component", - "Connection", - "DocumentController", - "Endpoint", - "Graph", - "GraphDocument", - "Icon", - "Port", -] diff --git a/BEdit/src/bedit/gui/__init__.py b/BEdit/src/bedit/gui/__init__.py new file mode 100644 index 0000000..faf6cbd --- /dev/null +++ b/BEdit/src/bedit/gui/__init__.py @@ -0,0 +1 @@ +"""Qt user interface and application adapters.""" diff --git a/BEdit/src/bedit/app.py b/BEdit/src/bedit/gui/app.py similarity index 97% rename from BEdit/src/bedit/app.py rename to BEdit/src/bedit/gui/app.py index 7d02232..1997f90 100644 --- a/BEdit/src/bedit/app.py +++ b/BEdit/src/bedit/gui/app.py @@ -4,7 +4,7 @@ from PySide6.QtCore import QCoreApplication, Qt from PySide6.QtGui import QColor, QPalette from PySide6.QtWidgets import QApplication, QStyleFactory -from bedit.main_window import MainWindow +from bedit.gui.main_window import MainWindow def apply_light_theme(app: QApplication) -> None: diff --git a/BEdit/src/bedit/gui/controllers/__init__.py b/BEdit/src/bedit/gui/controllers/__init__.py new file mode 100644 index 0000000..1580f20 --- /dev/null +++ b/BEdit/src/bedit/gui/controllers/__init__.py @@ -0,0 +1,5 @@ +"""Qt-aware application controllers and undo commands.""" + +from bedit.gui.controllers.document import DocumentController + +__all__ = ["DocumentController"] diff --git a/BEdit/src/bedit/document/commands.py b/BEdit/src/bedit/gui/controllers/commands.py similarity index 99% rename from BEdit/src/bedit/document/commands.py rename to BEdit/src/bedit/gui/controllers/commands.py index b3e2122..e428a30 100644 --- a/BEdit/src/bedit/document/commands.py +++ b/BEdit/src/bedit/gui/controllers/commands.py @@ -1,7 +1,7 @@ from PySide6.QtCore import QPointF from PySide6.QtGui import QUndoCommand -from bedit.document.model import Component, Connection, Port +from bedit.core.model import Component, Connection, Port class AddComponentCommand(QUndoCommand): diff --git a/BEdit/src/bedit/document/controller.py b/BEdit/src/bedit/gui/controllers/document.py similarity index 99% rename from BEdit/src/bedit/document/controller.py rename to BEdit/src/bedit/gui/controllers/document.py index dccc380..a588dc0 100644 --- a/BEdit/src/bedit/document/controller.py +++ b/BEdit/src/bedit/gui/controllers/document.py @@ -5,7 +5,7 @@ from uuid import uuid4 from PySide6.QtCore import QObject, QPointF, Signal from PySide6.QtGui import QUndoStack -from bedit.document.commands import ( +from bedit.gui.controllers.commands import ( AddComponentCommand, AddConnectionCommand, AddInterfacePortCommand, @@ -20,7 +20,7 @@ from bedit.document.commands import ( ReplaceSourceCommand, RotateComponentsCommand, ) -from bedit.document.model import ( +from bedit.core.model import ( Component, Connection, Endpoint, @@ -29,8 +29,8 @@ from bedit.document.model import ( Port, clone_component, ) -from bedit.document.port_types import PortTypeRegistry -from bedit.document.serializer import JsonDocumentSerializer +from bedit.core.port_types import PortTypeRegistry +from bedit.core.serializer import JsonDocumentSerializer class DocumentController(QObject): diff --git a/BEdit/src/bedit/gui/dialogs/__init__.py b/BEdit/src/bedit/gui/dialogs/__init__.py new file mode 100644 index 0000000..2cd21d6 --- /dev/null +++ b/BEdit/src/bedit/gui/dialogs/__init__.py @@ -0,0 +1 @@ +"""Application dialogs.""" diff --git a/BEdit/src/bedit/component_options_dialog.py b/BEdit/src/bedit/gui/dialogs/component_options.py similarity index 90% rename from BEdit/src/bedit/component_options_dialog.py rename to BEdit/src/bedit/gui/dialogs/component_options.py index b1252ef..a3f8a54 100644 --- a/BEdit/src/bedit/component_options_dialog.py +++ b/BEdit/src/bedit/gui/dialogs/component_options.py @@ -1,8 +1,8 @@ from PySide6.QtWidgets import QDialog, QMessageBox, QPushButton -from bedit.document.model import Component -from bedit.icon_editor import IconEditorDialog -from bedit.ui_component_options_dialog import Ui_ComponentOptionsDialog +from bedit.core.model import Component +from bedit.gui.graphics.icon_editor import IconEditorDialog +from bedit.gui.generated.ui_component_options_dialog import Ui_ComponentOptionsDialog class ComponentOptionsDialog(QDialog): diff --git a/BEdit/src/bedit/item_options_dialog.py b/BEdit/src/bedit/gui/dialogs/item_options.py similarity index 100% rename from BEdit/src/bedit/item_options_dialog.py rename to BEdit/src/bedit/gui/dialogs/item_options.py diff --git a/BEdit/src/bedit/port_options_dialog.py b/BEdit/src/bedit/gui/dialogs/port_options.py similarity index 98% rename from BEdit/src/bedit/port_options_dialog.py rename to BEdit/src/bedit/gui/dialogs/port_options.py index d5b3e33..dd8258a 100644 --- a/BEdit/src/bedit/port_options_dialog.py +++ b/BEdit/src/bedit/gui/dialogs/port_options.py @@ -19,8 +19,8 @@ from PySide6.QtWidgets import ( QWidget, ) -from bedit.document.model import Component, Port -from bedit.document.port_types import PortTypeRegistry +from bedit.core.model import Component, Port +from bedit.core.port_types import PortTypeRegistry PORT_ROLE = Qt.ItemDataRole.UserRole diff --git a/BEdit/src/bedit/settings_dialog.py b/BEdit/src/bedit/gui/dialogs/settings.py similarity index 97% rename from BEdit/src/bedit/settings_dialog.py rename to BEdit/src/bedit/gui/dialogs/settings.py index 24708b1..bc367e6 100644 --- a/BEdit/src/bedit/settings_dialog.py +++ b/BEdit/src/bedit/gui/dialogs/settings.py @@ -3,9 +3,9 @@ from pathlib import Path from PySide6.QtCore import QSettings, Signal from PySide6.QtWidgets import QDialog, QFileDialog, QFormLayout, QGroupBox, QSpinBox -from bedit.library.repository import default_library_paths -from bedit.preferences import application_settings -from bedit.ui_settings_dialog import Ui_SettingsDialog +from bedit.core.libraries import default_library_paths +from bedit.gui.preferences import application_settings +from bedit.gui.generated.ui_settings_dialog import Ui_SettingsDialog class SettingsDialog(QDialog): diff --git a/BEdit/src/bedit/gui/generated/__init__.py b/BEdit/src/bedit/gui/generated/__init__.py new file mode 100644 index 0000000..0a7c1ac --- /dev/null +++ b/BEdit/src/bedit/gui/generated/__init__.py @@ -0,0 +1 @@ +"""Generated Qt code. Do not edit these modules by hand.""" diff --git a/BEdit/src/bedit/resources_rc.py b/BEdit/src/bedit/gui/generated/resources_rc.py similarity index 100% rename from BEdit/src/bedit/resources_rc.py rename to BEdit/src/bedit/gui/generated/resources_rc.py diff --git a/BEdit/src/bedit/ui_component_options_dialog.py b/BEdit/src/bedit/gui/generated/ui_component_options_dialog.py similarity index 100% rename from BEdit/src/bedit/ui_component_options_dialog.py rename to BEdit/src/bedit/gui/generated/ui_component_options_dialog.py diff --git a/BEdit/src/bedit/ui_main_window.py b/BEdit/src/bedit/gui/generated/ui_main_window.py similarity index 99% rename from BEdit/src/bedit/ui_main_window.py rename to BEdit/src/bedit/gui/generated/ui_main_window.py index 1f447f3..38cb3d0 100644 --- a/BEdit/src/bedit/ui_main_window.py +++ b/BEdit/src/bedit/gui/generated/ui_main_window.py @@ -22,7 +22,7 @@ from PySide6.QtWidgets import (QApplication, QDockWidget, QFrame, QHBoxLayout, QSpacerItem, QSplitter, QStackedWidget, QToolBar, QToolButton, QTreeView, QVBoxLayout, QWidget) -from bedit.workspace.view import GraphWorkspaceView +from bedit.gui.graphics.workspace import GraphWorkspaceView from . import resources_rc class Ui_MainWindow(object): diff --git a/BEdit/src/bedit/ui_settings_dialog.py b/BEdit/src/bedit/gui/generated/ui_settings_dialog.py similarity index 100% rename from BEdit/src/bedit/ui_settings_dialog.py rename to BEdit/src/bedit/gui/generated/ui_settings_dialog.py diff --git a/BEdit/src/bedit/gui/graphics/__init__.py b/BEdit/src/bedit/gui/graphics/__init__.py new file mode 100644 index 0000000..1c4e281 --- /dev/null +++ b/BEdit/src/bedit/gui/graphics/__init__.py @@ -0,0 +1,5 @@ +"""Graphics scenes, views, editors, and renderers.""" + +from bedit.gui.graphics.workspace import GraphWorkspaceView + +__all__ = ["GraphWorkspaceView"] diff --git a/BEdit/src/bedit/icon_editor.py b/BEdit/src/bedit/gui/graphics/icon_editor.py similarity index 99% rename from BEdit/src/bedit/icon_editor.py rename to BEdit/src/bedit/gui/graphics/icon_editor.py index 503317e..420b000 100644 --- a/BEdit/src/bedit/icon_editor.py +++ b/BEdit/src/bedit/gui/graphics/icon_editor.py @@ -27,9 +27,9 @@ from PySide6.QtWidgets import ( QWidget, ) -from bedit.document.model import Component, Icon, Port -from bedit.icon_renderer import _pen -from bedit.preferences import application_settings +from bedit.core.model import Component, Icon, Port +from bedit.gui.graphics.icon_renderer import _pen +from bedit.gui.preferences import application_settings def _icon_grid_size() -> int: diff --git a/BEdit/src/bedit/icon_renderer.py b/BEdit/src/bedit/gui/graphics/icon_renderer.py similarity index 98% rename from BEdit/src/bedit/icon_renderer.py rename to BEdit/src/bedit/gui/graphics/icon_renderer.py index b260399..84f6f75 100644 --- a/BEdit/src/bedit/icon_renderer.py +++ b/BEdit/src/bedit/gui/graphics/icon_renderer.py @@ -1,7 +1,7 @@ from PySide6.QtCore import QPointF, QRectF, Qt from PySide6.QtGui import QColor, QFont, QIcon, QPainter, QPen, QPixmap, QPolygonF -from bedit.document.model import Icon +from bedit.core.model import Icon def icon_bounds(icon: Icon) -> QRectF: diff --git a/BEdit/src/bedit/workspace/view.py b/BEdit/src/bedit/gui/graphics/workspace.py similarity index 98% rename from BEdit/src/bedit/workspace/view.py rename to BEdit/src/bedit/gui/graphics/workspace.py index eea4f8b..398e377 100644 --- a/BEdit/src/bedit/workspace/view.py +++ b/BEdit/src/bedit/gui/graphics/workspace.py @@ -28,11 +28,11 @@ from PySide6.QtWidgets import ( QWidget, ) -from bedit.document.controller import DocumentController -from bedit.document.model import Component, Connection, Endpoint, Port -from bedit.library.tree_model import COMPONENT_MIME_TYPE -from bedit.icon_renderer import icon_bounds, paint_icon -from bedit.preferences import application_settings +from bedit.core.model import Component, Connection, Endpoint, Port +from bedit.gui.controllers.document import DocumentController +from bedit.gui.models.library_tree import COMPONENT_MIME_TYPE +from bedit.gui.graphics.icon_renderer import icon_bounds, paint_icon +from bedit.gui.preferences import application_settings SELECTION_MIME_TYPE = "application/x-bedit-selection" diff --git a/BEdit/src/bedit/main_window.py b/BEdit/src/bedit/gui/main_window.py similarity index 97% rename from BEdit/src/bedit/main_window.py rename to BEdit/src/bedit/gui/main_window.py index bc384ca..e113d62 100644 --- a/BEdit/src/bedit/main_window.py +++ b/BEdit/src/bedit/gui/main_window.py @@ -6,23 +6,23 @@ from PySide6.QtCore import QSize, Qt, Slot from PySide6.QtGui import QAction, QCloseEvent from PySide6.QtWidgets import QFileDialog, QMainWindow, QMenu, QMessageBox, QToolBar -from bedit.component_options_dialog import ComponentOptionsDialog -from bedit.document.controller import DocumentController -from bedit.document.model import Component, Port -from bedit.document.serializer import JsonDocumentSerializer -from bedit.item_options_dialog import ItemOptionsDialog -from bedit.library.repository import LibraryRepository -from bedit.library.tree_model import ( +from bedit.core.model import Component, Port +from bedit.core.serializer import JsonDocumentSerializer +from bedit.gui.controllers.document import DocumentController +from bedit.gui.dialogs.component_options import ComponentOptionsDialog +from bedit.gui.dialogs.item_options import ItemOptionsDialog +from bedit.gui.models.library_repository import LibraryRepository +from bedit.gui.models.library_tree import ( COMPONENT_ID_ROLE, COMPONENT_INSTANCE_ROLE, ITEM_KIND_ROLE, DocumentTreeModel, LibraryTreeModel, ) -from bedit.settings_dialog import SettingsDialog -from bedit.port_options_dialog import PortOptionsDialog -from bedit.preferences import application_settings -from bedit.ui_main_window import Ui_MainWindow +from bedit.gui.dialogs.settings import SettingsDialog +from bedit.gui.dialogs.port_options import PortOptionsDialog +from bedit.gui.preferences import application_settings +from bedit.gui.generated.ui_main_window import Ui_MainWindow class MainWindow(QMainWindow): diff --git a/BEdit/src/bedit/gui/models/__init__.py b/BEdit/src/bedit/gui/models/__init__.py new file mode 100644 index 0000000..bd5e1a4 --- /dev/null +++ b/BEdit/src/bedit/gui/models/__init__.py @@ -0,0 +1 @@ +"""Qt model/view adapters and repositories.""" diff --git a/BEdit/src/bedit/gui/models/library_repository.py b/BEdit/src/bedit/gui/models/library_repository.py new file mode 100644 index 0000000..df37009 --- /dev/null +++ b/BEdit/src/bedit/gui/models/library_repository.py @@ -0,0 +1,31 @@ +import json +from pathlib import Path + +from PySide6.QtCore import QObject, Signal + +from bedit.core.libraries import LibraryDocument, library_candidates, load_library_file + + +class LibraryRepository(QObject): + librariesChanged = Signal() + loadWarningsChanged = Signal(list) + + def __init__(self, parent=None) -> None: + super().__init__(parent) + self.libraries: list[LibraryDocument] = [] + self.load_warnings: list[str] = [] + + def load_paths(self, paths: list[str]) -> None: + libraries: list[LibraryDocument] = [] + warnings: list[str] = [] + for raw_path in paths: + path = Path(raw_path).expanduser() + for candidate in library_candidates(path): + try: + libraries.append(load_library_file(candidate)) + except (OSError, ValueError, KeyError, TypeError, json.JSONDecodeError) as error: + warnings.append(f"{candidate}: {error}") + self.libraries = libraries + self.load_warnings = warnings + self.librariesChanged.emit() + self.loadWarningsChanged.emit(warnings) diff --git a/BEdit/src/bedit/library/tree_model.py b/BEdit/src/bedit/gui/models/library_tree.py similarity index 94% rename from BEdit/src/bedit/library/tree_model.py rename to BEdit/src/bedit/gui/models/library_tree.py index b85e80a..c08fe2d 100644 --- a/BEdit/src/bedit/library/tree_model.py +++ b/BEdit/src/bedit/gui/models/library_tree.py @@ -3,10 +3,10 @@ import json from PySide6.QtCore import QByteArray, QMimeData, QModelIndex, Qt, Signal from PySide6.QtGui import QStandardItem, QStandardItemModel -from bedit.document.controller import DocumentController -from bedit.document.model import Component -from bedit.icon_renderer import library_icon -from bedit.library.repository import LibraryRepository +from bedit.core.model import Component +from bedit.gui.controllers.document import DocumentController +from bedit.gui.graphics.icon_renderer import library_icon +from bedit.gui.models.library_repository import LibraryRepository COMPONENT_ROLE = Qt.ItemDataRole.UserRole + 1 diff --git a/BEdit/src/bedit/preferences.py b/BEdit/src/bedit/gui/preferences.py similarity index 100% rename from BEdit/src/bedit/preferences.py rename to BEdit/src/bedit/gui/preferences.py diff --git a/BEdit/src/bedit/library/__init__.py b/BEdit/src/bedit/library/__init__.py deleted file mode 100644 index a73536b..0000000 --- a/BEdit/src/bedit/library/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from bedit.library.repository import LibraryRepository, default_library_paths - -__all__ = ["LibraryRepository", "default_library_paths"] diff --git a/BEdit/src/bedit/library/repository.py b/BEdit/src/bedit/library/repository.py deleted file mode 100644 index 448a85a..0000000 --- a/BEdit/src/bedit/library/repository.py +++ /dev/null @@ -1,56 +0,0 @@ -import json -from dataclasses import dataclass -from pathlib import Path - -from PySide6.QtCore import QObject, Signal - -from bedit.document.model import GraphDocument - - -@dataclass(frozen=True) -class LibraryDocument: - name: str - document: GraphDocument - source_path: str - - -def bundled_library_path() -> Path: - return Path(__file__).resolve().parents[1] / "data" / "libraries" / "example.json" - - -def default_library_paths() -> list[str]: - return [str(bundled_library_path())] - - -class LibraryRepository(QObject): - librariesChanged = Signal() - loadWarningsChanged = Signal(list) - - def __init__(self, parent=None) -> None: - super().__init__(parent) - self.libraries: list[LibraryDocument] = [] - self.load_warnings: list[str] = [] - - def load_paths(self, paths: list[str]) -> None: - libraries: list[LibraryDocument] = [] - warnings: list[str] = [] - for raw_path in paths: - path = Path(raw_path).expanduser() - candidates = sorted(path.glob("*.json")) if path.is_dir() else [path] - for candidate in candidates: - try: - libraries.append(self._load_file(candidate)) - except (OSError, ValueError, KeyError, TypeError, json.JSONDecodeError) as error: - warnings.append(f"{candidate}: {error}") - self.libraries = libraries - self.load_warnings = warnings - self.librariesChanged.emit() - self.loadWarningsChanged.emit(warnings) - - @staticmethod - def _load_file(path: Path) -> LibraryDocument: - with path.open(encoding="utf-8") as file: - data = json.load(file) - document = GraphDocument.from_dict(data) - name = str(document.metadata.get("name") or path.stem) - return LibraryDocument(name, document, str(path)) diff --git a/BEdit/src/bedit/workspace/__init__.py b/BEdit/src/bedit/workspace/__init__.py deleted file mode 100644 index 72dae2f..0000000 --- a/BEdit/src/bedit/workspace/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from bedit.workspace.view import GraphWorkspaceView - -__all__ = ["GraphWorkspaceView"] - diff --git a/BEdit/test.bedit.json b/BEdit/test.bedit.json index a79b1f5..4f2e98f 100644 --- a/BEdit/test.bedit.json +++ b/BEdit/test.bedit.json @@ -64,7 +64,7 @@ "name": "New Graph Block 1", "position": { "x": 0.0, - "y": -96.0 + "y": -64.0 }, "rotation": 0.0, "interface": { diff --git a/BEdit/ui/main_window.ui b/BEdit/ui/main_window.ui index 36b48e3..a4f294c 100644 --- a/BEdit/ui/main_window.ui +++ b/BEdit/ui/main_window.ui @@ -484,7 +484,7 @@ GraphWorkspaceView QGraphicsView -
bedit.workspace.view
+
bedit.gui.graphics.workspace
diff --git a/BEdit/ui/main_window_ui.py b/BEdit/ui/main_window_ui.py new file mode 100644 index 0000000..1c98bf4 --- /dev/null +++ b/BEdit/ui/main_window_ui.py @@ -0,0 +1,450 @@ +# -*- 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, QFrame, QHBoxLayout, + QHeaderView, QLabel, QMainWindow, QMenu, + QMenuBar, QPlainTextEdit, QPushButton, QSizePolicy, + QSpacerItem, QSplitter, QStackedWidget, QToolBar, + QToolButton, QTreeView, QVBoxLayout, QWidget) + +from bedit.gui.graphics.workspace import GraphWorkspaceView +import resources_rc + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + if not MainWindow.objectName(): + MainWindow.setObjectName(u"MainWindow") + MainWindow.resize(1000, 700) + self.actionNew = QAction(MainWindow) + self.actionNew.setObjectName(u"actionNew") + icon = QIcon() + icon.addFile(u":/icons/icons/document-new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + self.actionNew.setIcon(icon) + self.actionRotateClockwise = QAction(MainWindow) + self.actionRotateClockwise.setObjectName(u"actionRotateClockwise") + icon1 = QIcon() + icon1.addFile(u":/icons/icons/transform-rotate.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + self.actionRotateClockwise.setIcon(icon1) + self.actionOpen = QAction(MainWindow) + self.actionOpen.setObjectName(u"actionOpen") + icon2 = QIcon() + icon2.addFile(u":/icons/icons/document-open.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + self.actionOpen.setIcon(icon2) + self.actionSave = QAction(MainWindow) + self.actionSave.setObjectName(u"actionSave") + icon3 = QIcon() + icon3.addFile(u":/icons/icons/document-save.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + self.actionSave.setIcon(icon3) + self.actionSaveAs = QAction(MainWindow) + self.actionSaveAs.setObjectName(u"actionSaveAs") + icon4 = QIcon() + icon4.addFile(u":/icons/icons/document-save-as.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + self.actionSaveAs.setIcon(icon4) + self.actionExit = QAction(MainWindow) + self.actionExit.setObjectName(u"actionExit") + self.actionClose = QAction(MainWindow) + self.actionClose.setObjectName(u"actionClose") + 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.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.actionCut = QAction(MainWindow) + self.actionCut.setObjectName(u"actionCut") + icon7 = QIcon() + icon7.addFile(u":/icons/icons/edit-cut.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + self.actionCut.setIcon(icon7) + 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.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.actionSelectAll = QAction(MainWindow) + self.actionSelectAll.setObjectName(u"actionSelectAll") + self.actionDelete = QAction(MainWindow) + self.actionDelete.setObjectName(u"actionDelete") + self.actionAbout = QAction(MainWindow) + self.actionAbout.setObjectName(u"actionAbout") + self.actionSettings = QAction(MainWindow) + self.actionSettings.setObjectName(u"actionSettings") + self.actionAboutQt = QAction(MainWindow) + self.actionAboutQt.setObjectName(u"actionAboutQt") + self.centralwidget = QWidget(MainWindow) + self.centralwidget.setObjectName(u"centralwidget") + self.workspaceLayout = QHBoxLayout(self.centralwidget) + self.workspaceLayout.setSpacing(0) + self.workspaceLayout.setObjectName(u"workspaceLayout") + self.workspaceLayout.setContentsMargins(0, 0, 0, 0) + self.workspaceSplitter = QSplitter(self.centralwidget) + self.workspaceSplitter.setObjectName(u"workspaceSplitter") + sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.workspaceSplitter.sizePolicy().hasHeightForWidth()) + self.workspaceSplitter.setSizePolicy(sizePolicy) + self.workspaceSplitter.setOrientation(Qt.Orientation.Horizontal) + self.workspaceSplitter.setChildrenCollapsible(False) + self.leftDockHost = QMainWindow(self.workspaceSplitter) + self.leftDockHost.setObjectName(u"leftDockHost") + self.leftDockHost.setMinimumSize(QSize(220, 0)) + self.panel_libraries = QDockWidget(self.leftDockHost) + self.panel_libraries.setObjectName(u"panel_libraries") + self.panel_libraries.setMinimumSize(QSize(220, 91)) + self.dockWidgetContents = QWidget() + self.dockWidgetContents.setObjectName(u"dockWidgetContents") + self.librariesLayout = QVBoxLayout(self.dockWidgetContents) + self.librariesLayout.setSpacing(0) + self.librariesLayout.setObjectName(u"librariesLayout") + self.librariesLayout.setContentsMargins(0, 0, 0, 0) + self.treeView = QTreeView(self.dockWidgetContents) + self.treeView.setObjectName(u"treeView") + self.treeView.setAlternatingRowColors(True) + self.treeView.setUniformRowHeights(True) + + self.librariesLayout.addWidget(self.treeView) + + self.panel_libraries.setWidget(self.dockWidgetContents) + self.leftDockHost.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, self.panel_libraries) + self.panel_document = QDockWidget(self.leftDockHost) + self.panel_document.setObjectName(u"panel_document") + self.panel_document.setMinimumSize(QSize(220, 91)) + self.documentDockContents = QWidget() + self.documentDockContents.setObjectName(u"documentDockContents") + self.documentPanelLayout = QVBoxLayout(self.documentDockContents) + self.documentPanelLayout.setSpacing(0) + self.documentPanelLayout.setObjectName(u"documentPanelLayout") + self.documentPanelLayout.setContentsMargins(0, 0, 0, 0) + self.documentTreeView = QTreeView(self.documentDockContents) + self.documentTreeView.setObjectName(u"documentTreeView") + self.documentTreeView.setAlternatingRowColors(True) + self.documentTreeView.setUniformRowHeights(True) + + self.documentPanelLayout.addWidget(self.documentTreeView) + + self.panel_document.setWidget(self.documentDockContents) + self.leftDockHost.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, self.panel_document) + self.workspaceSplitter.addWidget(self.leftDockHost) + self.workspace = QWidget(self.workspaceSplitter) + self.workspace.setObjectName(u"workspace") + sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) + sizePolicy1.setHorizontalStretch(1) + sizePolicy1.setVerticalStretch(0) + sizePolicy1.setHeightForWidth(self.workspace.sizePolicy().hasHeightForWidth()) + self.workspace.setSizePolicy(sizePolicy1) + self.workspaceEditorLayout = QVBoxLayout(self.workspace) + self.workspaceEditorLayout.setSpacing(0) + self.workspaceEditorLayout.setObjectName(u"workspaceEditorLayout") + self.workspaceEditorLayout.setContentsMargins(0, 0, 0, 0) + self.workspaceHeader = QFrame(self.workspace) + self.workspaceHeader.setObjectName(u"workspaceHeader") + self.workspaceHeader.setMinimumSize(QSize(0, 34)) + self.workspaceHeader.setMaximumSize(QSize(16777215, 34)) + self.workspaceHeader.setFrameShape(QFrame.Shape.StyledPanel) + self.workspaceHeaderLayout = QHBoxLayout(self.workspaceHeader) + self.workspaceHeaderLayout.setObjectName(u"workspaceHeaderLayout") + self.workspaceHeaderLayout.setContentsMargins(6, 2, 6, 2) + self.navigateUpButton = QToolButton(self.workspaceHeader) + self.navigateUpButton.setObjectName(u"navigateUpButton") + + self.workspaceHeaderLayout.addWidget(self.navigateUpButton) + + self.graphBreadcrumbLabel = QLabel(self.workspaceHeader) + self.graphBreadcrumbLabel.setObjectName(u"graphBreadcrumbLabel") + + self.workspaceHeaderLayout.addWidget(self.graphBreadcrumbLabel) + + self.workspaceModeLabel = QLabel(self.workspaceHeader) + self.workspaceModeLabel.setObjectName(u"workspaceModeLabel") + + self.workspaceHeaderLayout.addWidget(self.workspaceModeLabel) + + self.workspaceHeaderSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) + + self.workspaceHeaderLayout.addItem(self.workspaceHeaderSpacer) + + self.applyJsonButton = QPushButton(self.workspaceHeader) + self.applyJsonButton.setObjectName(u"applyJsonButton") + self.applyJsonButton.setVisible(False) + + self.workspaceHeaderLayout.addWidget(self.applyJsonButton) + + self.pointerToolButton = QToolButton(self.workspaceHeader) + self.pointerToolButton.setObjectName(u"pointerToolButton") + self.pointerToolButton.setCheckable(True) + self.pointerToolButton.setChecked(True) + self.pointerToolButton.setAutoExclusive(True) + + self.workspaceHeaderLayout.addWidget(self.pointerToolButton) + + self.inputToolButton = QToolButton(self.workspaceHeader) + self.inputToolButton.setObjectName(u"inputToolButton") + self.inputToolButton.setCheckable(True) + self.inputToolButton.setAutoExclusive(True) + + self.workspaceHeaderLayout.addWidget(self.inputToolButton) + + self.outputToolButton = QToolButton(self.workspaceHeader) + self.outputToolButton.setObjectName(u"outputToolButton") + self.outputToolButton.setCheckable(True) + self.outputToolButton.setAutoExclusive(True) + + self.workspaceHeaderLayout.addWidget(self.outputToolButton) + + + self.workspaceEditorLayout.addWidget(self.workspaceHeader) + + self.workspaceStack = QStackedWidget(self.workspace) + self.workspaceStack.setObjectName(u"workspaceStack") + self.graphPage = QWidget() + self.graphPage.setObjectName(u"graphPage") + self.graphPageLayout = QVBoxLayout(self.graphPage) + self.graphPageLayout.setObjectName(u"graphPageLayout") + self.graphPageLayout.setContentsMargins(0, 0, 0, 0) + self.graphView = GraphWorkspaceView(self.graphPage) + self.graphView.setObjectName(u"graphView") + + self.graphPageLayout.addWidget(self.graphView) + + self.workspaceStack.addWidget(self.graphPage) + self.jsonPage = QWidget() + self.jsonPage.setObjectName(u"jsonPage") + self.jsonPageLayout = QVBoxLayout(self.jsonPage) + self.jsonPageLayout.setObjectName(u"jsonPageLayout") + self.jsonPageLayout.setContentsMargins(0, 0, 0, 0) + self.jsonEditor = QPlainTextEdit(self.jsonPage) + self.jsonEditor.setObjectName(u"jsonEditor") + self.jsonEditor.setLineWrapMode(QPlainTextEdit.LineWrapMode.NoWrap) + + self.jsonPageLayout.addWidget(self.jsonEditor) + + self.workspaceStack.addWidget(self.jsonPage) + self.emptyPage = QWidget() + self.emptyPage.setObjectName(u"emptyPage") + self.emptyPageLayout = QVBoxLayout(self.emptyPage) + self.emptyPageLayout.setObjectName(u"emptyPageLayout") + self.emptyWorkspaceLabel = QLabel(self.emptyPage) + self.emptyWorkspaceLabel.setObjectName(u"emptyWorkspaceLabel") + self.emptyWorkspaceLabel.setAlignment(Qt.AlignmentFlag.AlignCenter) + + self.emptyPageLayout.addWidget(self.emptyWorkspaceLabel) + + self.workspaceStack.addWidget(self.emptyPage) + + self.workspaceEditorLayout.addWidget(self.workspaceStack) + + self.workspaceSplitter.addWidget(self.workspace) + + self.workspaceLayout.addWidget(self.workspaceSplitter) + + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QMenuBar(MainWindow) + self.menubar.setObjectName(u"menubar") + self.menubar.setGeometry(QRect(0, 0, 1000, 24)) + 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.menuPanels = QMenu(self.menuView) + self.menuPanels.setObjectName(u"menuPanels") + self.menuToolbars = QMenu(self.menuView) + self.menuToolbars.setObjectName(u"menuToolbars") + self.menuHelp = QMenu(self.menubar) + self.menuHelp.setObjectName(u"menuHelp") + MainWindow.setMenuBar(self.menubar) + self.fileToolbar = QToolBar(MainWindow) + self.fileToolbar.setObjectName(u"fileToolbar") + sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) + sizePolicy2.setHorizontalStretch(0) + sizePolicy2.setVerticalStretch(0) + sizePolicy2.setHeightForWidth(self.fileToolbar.sizePolicy().hasHeightForWidth()) + self.fileToolbar.setSizePolicy(sizePolicy2) + self.fileToolbar.setMinimumSize(QSize(0, 40)) + self.fileToolbar.setMaximumSize(QSize(16777215, 40)) + self.fileToolbar.setIconSize(QSize(24, 24)) + MainWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.fileToolbar) + self.editToolbar = QToolBar(MainWindow) + self.editToolbar.setObjectName(u"editToolbar") + MainWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.editToolbar) + self.transformToolbar = QToolBar(MainWindow) + self.transformToolbar.setObjectName(u"transformToolbar") + MainWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.transformToolbar) + + self.menubar.addAction(self.menuFile.menuAction()) + self.menubar.addAction(self.menuEdit.menuAction()) + self.menubar.addAction(self.menuView.menuAction()) + self.menubar.addAction(self.menuHelp.menuAction()) + self.menuFile.addAction(self.actionNew) + self.menuFile.addAction(self.actionOpen) + self.menuFile.addSeparator() + self.menuFile.addAction(self.actionSave) + self.menuFile.addAction(self.actionSaveAs) + self.menuFile.addSeparator() + self.menuFile.addAction(self.actionClose) + self.menuFile.addAction(self.actionExit) + 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.addAction(self.actionDelete) + self.menuEdit.addAction(self.actionSelectAll) + self.menuEdit.addSeparator() + self.menuEdit.addAction(self.actionSettings) + self.menuView.addAction(self.menuPanels.menuAction()) + self.menuView.addAction(self.menuToolbars.menuAction()) + self.menuHelp.addAction(self.actionAbout) + self.menuHelp.addAction(self.actionAboutQt) + self.fileToolbar.addAction(self.actionNew) + self.fileToolbar.addAction(self.actionOpen) + self.fileToolbar.addAction(self.actionSave) + self.fileToolbar.addAction(self.actionSaveAs) + self.editToolbar.addAction(self.actionUndo) + self.editToolbar.addAction(self.actionRedo) + self.editToolbar.addAction(self.actionCopy) + self.editToolbar.addAction(self.actionCut) + self.editToolbar.addAction(self.actionPaste) + self.transformToolbar.addAction(self.actionRotateClockwise) + + self.retranslateUi(MainWindow) + + self.workspaceStack.setCurrentIndex(0) + + + QMetaObject.connectSlotsByName(MainWindow) + # setupUi + + def retranslateUi(self, MainWindow): + MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"BEdit", None)) + self.actionNew.setText(QCoreApplication.translate("MainWindow", u"&New", None)) +#if QT_CONFIG(statustip) + self.actionNew.setStatusTip(QCoreApplication.translate("MainWindow", u"Create a new document", None)) +#endif // QT_CONFIG(statustip) +#if QT_CONFIG(shortcut) + self.actionNew.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+N", None)) +#endif // QT_CONFIG(shortcut) + self.actionRotateClockwise.setText(QCoreApplication.translate("MainWindow", u"Rotate Clockwise", None)) +#if QT_CONFIG(tooltip) + self.actionRotateClockwise.setToolTip(QCoreApplication.translate("MainWindow", u"Rotate selected blocks clockwise by 90 degrees", None)) +#endif // QT_CONFIG(tooltip) +#if QT_CONFIG(shortcut) + self.actionRotateClockwise.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+R", None)) +#endif // QT_CONFIG(shortcut) + self.actionOpen.setText(QCoreApplication.translate("MainWindow", u"&Open\u2026", None)) +#if QT_CONFIG(statustip) + self.actionOpen.setStatusTip(QCoreApplication.translate("MainWindow", u"Open a document", None)) +#endif // QT_CONFIG(statustip) +#if QT_CONFIG(shortcut) + self.actionOpen.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+O", None)) +#endif // QT_CONFIG(shortcut) + self.actionSave.setText(QCoreApplication.translate("MainWindow", u"&Save", None)) +#if QT_CONFIG(statustip) + self.actionSave.setStatusTip(QCoreApplication.translate("MainWindow", u"Save the current document", None)) +#endif // QT_CONFIG(statustip) +#if QT_CONFIG(shortcut) + self.actionSave.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+S", None)) +#endif // QT_CONFIG(shortcut) + self.actionSaveAs.setText(QCoreApplication.translate("MainWindow", u"Save &As\u2026", None)) +#if QT_CONFIG(shortcut) + self.actionSaveAs.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+Shift+S", None)) +#endif // QT_CONFIG(shortcut) + self.actionExit.setText(QCoreApplication.translate("MainWindow", u"E&xit", None)) +#if QT_CONFIG(shortcut) + self.actionExit.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+Q", None)) +#endif // QT_CONFIG(shortcut) + self.actionClose.setText(QCoreApplication.translate("MainWindow", u"&Close Document", None)) +#if QT_CONFIG(shortcut) + self.actionClose.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+W", None)) +#endif // QT_CONFIG(shortcut) + 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.actionCut.setText(QCoreApplication.translate("MainWindow", u"Cu&t", None)) +#if QT_CONFIG(shortcut) + self.actionCut.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+X", None)) +#endif // QT_CONFIG(shortcut) + self.actionCopy.setText(QCoreApplication.translate("MainWindow", u"&Copy", None)) +#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(shortcut) + self.actionPaste.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+V", None)) +#endif // QT_CONFIG(shortcut) + self.actionSelectAll.setText(QCoreApplication.translate("MainWindow", u"Select &All", None)) +#if QT_CONFIG(shortcut) + self.actionSelectAll.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+A", None)) +#endif // QT_CONFIG(shortcut) + self.actionDelete.setText(QCoreApplication.translate("MainWindow", u"&Delete", None)) +#if QT_CONFIG(shortcut) + self.actionDelete.setShortcut(QCoreApplication.translate("MainWindow", u"Del", None)) +#endif // QT_CONFIG(shortcut) + self.actionAbout.setText(QCoreApplication.translate("MainWindow", u"&About BEdit", None)) + self.actionSettings.setText(QCoreApplication.translate("MainWindow", u"&Settings\u2026", None)) +#if QT_CONFIG(statustip) + self.actionSettings.setStatusTip(QCoreApplication.translate("MainWindow", u"Configure BEdit", None)) +#endif // QT_CONFIG(statustip) + self.actionAboutQt.setText(QCoreApplication.translate("MainWindow", u"About &Qt", None)) + self.panel_libraries.setWindowTitle(QCoreApplication.translate("MainWindow", u"Libraries", None)) + self.panel_document.setWindowTitle(QCoreApplication.translate("MainWindow", u"Document", None)) + self.navigateUpButton.setText(QCoreApplication.translate("MainWindow", u"Up", None)) +#if QT_CONFIG(tooltip) + self.navigateUpButton.setToolTip(QCoreApplication.translate("MainWindow", u"Open the containing graph", None)) +#endif // QT_CONFIG(tooltip) + self.graphBreadcrumbLabel.setText(QCoreApplication.translate("MainWindow", u"Untitled", None)) + self.workspaceModeLabel.setText(QCoreApplication.translate("MainWindow", u"Graph", None)) + self.applyJsonButton.setText(QCoreApplication.translate("MainWindow", u"Apply JSON", None)) + self.pointerToolButton.setText(QCoreApplication.translate("MainWindow", u"Pointer", None)) + self.inputToolButton.setText(QCoreApplication.translate("MainWindow", u"Input", None)) +#if QT_CONFIG(tooltip) + self.inputToolButton.setToolTip(QCoreApplication.translate("MainWindow", u"Add an interface input", None)) +#endif // QT_CONFIG(tooltip) + self.outputToolButton.setText(QCoreApplication.translate("MainWindow", u"Output", None)) +#if QT_CONFIG(tooltip) + self.outputToolButton.setToolTip(QCoreApplication.translate("MainWindow", u"Add an interface output", None)) +#endif // QT_CONFIG(tooltip) + self.jsonEditor.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Component JSON", None)) + self.emptyWorkspaceLabel.setText(QCoreApplication.translate("MainWindow", u"No document open", 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.menuPanels.setTitle(QCoreApplication.translate("MainWindow", u"&Panels", None)) + self.menuToolbars.setTitle(QCoreApplication.translate("MainWindow", u"&Toolbars", None)) + self.menuHelp.setTitle(QCoreApplication.translate("MainWindow", u"&Help", None)) + self.fileToolbar.setWindowTitle(QCoreApplication.translate("MainWindow", u"File", None)) + self.editToolbar.setWindowTitle(QCoreApplication.translate("MainWindow", u"Edit", None)) + self.transformToolbar.setWindowTitle(QCoreApplication.translate("MainWindow", u"Transform", None)) + # retranslateUi +