Compare commits

..

2 Commits

Author SHA1 Message Date
42bf09610b Added binary file format 2026-07-20 15:39:34 +02:00
9ed8e371ef added stub simulation entry and logs 2026-07-20 15:26:18 +02:00
30 changed files with 1152 additions and 583 deletions

View File

@@ -24,7 +24,8 @@ src/bedit/
│ ├── model.py # Document, component, graph, port, icon data │ ├── model.py # Document, component, graph, port, icon data
│ ├── port_types.py # Port type definitions and compatibility │ ├── port_types.py # Port type definitions and compatibility
│ ├── serializer.py # JSON persistence │ ├── serializer.py # JSON persistence
── libraries.py # Library file discovery and parsing ── libraries.py # Library file discovery and parsing
│ └── simulation/ # Qt-free simulation service and compiler/runtime code
└── gui/ # All Qt-dependent code └── gui/ # All Qt-dependent code
├── app.py # QApplication startup and palette ├── app.py # QApplication startup and palette
├── main_window.py # Top-level UI orchestration ├── main_window.py # Top-level UI orchestration
@@ -91,6 +92,9 @@ Keep the root of `src/bedit` minimal. New UI modules should go into the relevant
- Avoid the QSettings group name `general`; Qt treats `General` specially in INI - Avoid the QSettings group name `general`; Qt treats `General` specially in INI
files. Autosave keys live under `autosave/`. files. Autosave keys live under `autosave/`.
- User-visible document edits should participate in undo/redo. - User-visible document edits should participate in undo/redo.
- Application-wide messages use `core.application_log.get_logger()`. The main
window installs the Qt log-panel handler; core code must only use standard
Python logging and must not import the GUI handler.
## Qt Designer and generated files ## Qt Designer and generated files
@@ -127,6 +131,9 @@ pyside6-uic --from-imports ui/icon_editor_dialog.ui \
pyside6-uic --from-imports ui/text_definition_editor.ui \ pyside6-uic --from-imports ui/text_definition_editor.ui \
-o src/bedit/gui/generated/ui_text_definition_editor.py -o src/bedit/gui/generated/ui_text_definition_editor.py
pyside6-uic --from-imports ui/simulation_settings_dialog.ui \
-o src/bedit/gui/generated/ui_simulation_settings_dialog.py
``` ```
When adding a promoted/custom widget in Designer, its header must use the real When adding a promoted/custom widget in Designer, its header must use the real
@@ -155,6 +162,9 @@ runtime. A tiny generic prompt with one field and OK/Cancel may remain code-only
## Document and library files ## Document and library files
- Documents use the `bedit-document` JSON format. - Documents use the `bedit-document` JSON format.
- Documents can be stored as human-readable `.bedit.json`/`.json` through
`JsonDocumentSerializer`, or as compressed MessagePack `.beb` through
`BebDocumentSerializer`. UI document I/O dispatches via `DocumentSerializer`.
- `test.bedit.json` is a useful manually created example during development. - `test.bedit.json` is a useful manually created example during development.
- Library documents use the same recursive document model. - Library documents use the same recursive document model.
- Library parsing belongs in `core/libraries.py`; Qt change notifications belong - Library parsing belongs in `core/libraries.py`; Qt change notifications belong

View File

@@ -146,7 +146,7 @@ BEdit document used as a copy source. The built-in example defines A, B, and C.
- File → Close Document removes the active document and returns to an empty - File → Close Document removes the active document and returns to an empty
workspace. An open graph uses a light gray grid whose visible and snapping workspace. An open graph uses a light gray grid whose visible and snapping
spacing are configured separately. spacing are configured separately.
- Edit → Settings → Libraries accepts document files or folders of JSON files. - Edit → Settings → Libraries accepts JSON or `.beb` document files and folders.
Every component owns its ports, declarative icon, properties, and child graph: Every component owns its ports, declarative icon, properties, and child graph:

View File

@@ -9,6 +9,7 @@ description = "A starter Qt desktop application"
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"
dependencies = [ dependencies = [
"msgpack>=1.0,<2",
"PySide6>=6.7,<7", "PySide6>=6.7,<7",
] ]
@@ -25,7 +26,7 @@ bedit = "bedit.gui.app:main"
where = ["src"] where = ["src"]
[tool.setuptools.package-data] [tool.setuptools.package-data]
bedit = ["data/libraries/*.json"] bedit = ["data/libraries/*.json", "data/libraries/*.beb"]
[tool.ruff] [tool.ruff]
line-length = 100 line-length = 100

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -1,5 +1,7 @@
<RCC> <RCC>
<qresource prefix="icons"> <qresource prefix="icons">
<file>icons/run-build.png</file>
<file>icons/preferences-system.png</file>
<file>icons/draw-triangle.png</file> <file>icons/draw-triangle.png</file>
<file>icons/draw-ellipse.png</file> <file>icons/draw-ellipse.png</file>
<file>icons/draw-circle.png</file> <file>icons/draw-circle.png</file>

View File

@@ -215,6 +215,129 @@ ff\xa6\x5cP`\xb7\xadY\x1b\xde\x8b\xcb\x97/\xc3\
\x80\x90\x84\xfe\x83\xfe\x0fl\x00\x00/\xbf\xf2\xf2\x7f\x9c\ \x80\x90\x84\xfe\x83\xfe\x0fl\x00\x00/\xbf\xf2\xf2\x7f\x9c\
\xfd\x0bVu/\x8d\xbcS'\xfc\x00\x00\x00\x00IE\ \xfd\x0bVu/\x8d\xbcS'\xfc\x00\x00\x00\x00IE\
ND\xaeB`\x82\ ND\xaeB`\x82\
\x00\x00\x07\x88\
\x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\
\x09pHYs\x00\x00\x0d\xd7\x00\x00\x0d\xd7\x01B(\
\x9bx\x00\x00\x00\x07tIME\x07\xd9\x08\x0b\x00-\
\x1f\xb3\xb6|\x9d\x00\x00\x00\x06bKGD\x00\xff\x00\
\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x07\x08IDATx\
\xda\xbdWiLTW\x18\x95\xda\xa0P1]b\x89\
\x0a.EQ\xdcj\x5c\xd3FcMj\xda\xa4I-\
?J4\xc4(1\xadMl\xd5\x22*h,\x88K\
\xd0b+\xa6\x15Y\x0dV\x94\x08U\x16A\x10\x18`\
f`\x18\x96a\x1f\x86a\xd8\x94}\x11T\x90\xa0v\
N\xefy\x99\x99L\xa8\x88&\xd6\x97|\xb9\xf7\xdd\xf7\
\xbes\xce\xb7\xdc\xfbf&\xbc\xea\x15\x10\x10\xc0a\xa2\
\xb0\x0f\xd7\xaf_\xbfx\xcd\x9a5\x8b\xc5\xdcQ\xd8\xdb\
\x9e\x9e\x9e\x13\xde\xc4\xf5\x96L&\xd3<z\xf4\x08C\
CC\x92q\x9e\x9e\x9e\xae\xe6\xb37!`jbb\
b\xdf\xc8\xc8\x08\x86\x87\x87%\xe3\xfc\xc6\x8d\x1b\xbd|\
\xf6:\x89\xec\x97/_\xfe\xc9s\xd6?\xc8\xcc\xcc\x1c\
z\xf2\xe4\x09\x89%\xe3<##c\x88\xcfF\xbf\xbc\
z\xf5\xeaO\xc5`\xf7\xaa\xe4\xd3\x93\x92\x92\xaa\x9e>\
}\x0a__\xdf\x10\xa6\x965\xde\xbcy\xb3\xfb\xb5k\
\xd7\xd2\xef\xdf\xbf\x8f\xc1\xc1AK\x098\xef\xeb\xebC\
\x5c\x5c\x5c\xba\xbb\xbb\xfb7|W\x98MHH\xc8\x9f\
\xcf\x9e=CUU\x95\x8e\x98/K\xee\x94\x9b\x9b\xdb\
A\xd0\x81\x81\x01#\xeb\x1b\x15\x15\x95\xabP(\x1a\x19\
-\xd7\x1f>|\xf8\x5c\xe3\xb3\xc7\x8f\x1fC\xa9T6\
\xc4\xc7\xc7\xcb9'F\x7f\x7f?t:]\x0f\xb1\xc7\
\xad\xef\xcd\x9b7k\xe8\xd0\xdb\xdbKcd\x12\xb8X\
32ra\x9c\x8f6\xae[\x9bQ\x10K\xfe==\
=\xe8\xec\xecDww7D`\xcc\x84\xc3\x98\xecK\
\x96,\xf1lllDGG\x87\x91Nt\xb6\x16B\
#\x01\x05\xb5\xb7\xb7\xa3\xa6F+\x22\xd3\xa2\xad\xfd\x1e\
\x06\xfa-\x844\x89\xb0\xab\xabKz\xef\xee\xdd\xbbh\
ii1655\xc1\xcd\xcdm\xdb\x8b20\xcd\xcb\
\xcb\xab\x90\xce\xad\xad\xad\x14B \x8b\x08FZZZ\
\x0a\x95J\x85\xb2\xb22\x18\x0c\x06\xd4\xd7\xd7\xa3\xb4\xbc\
\x14\x05\xea<\xa85\x0atuw\x8a\x88\xbb\x18\xb5\x85\
\x9cAq\xdc\xb1c\x87\x8a\x1c\xe3\x95a\x9e\xbf\xbf\x7f\
3\x05\xd0\x89 \x8c\x88\xf3\xc2\xc2Bid$4\x02\
744\x083P\x8c4\x97\x17fA\xdf(\xb2\xd2\
\xd6\x86{\xf7\xeeI\xebuuu8r\xe4H3\xb1\
_\xa6\x09\xa7\x5c\xbdz\xb5\xddD\xc2\xd4ID\x15\x15\
\x15\xbc7Y\xb3467\xb7H\xf3\xc6\x86F)\x13\
z\xbd^\x90\xe9Q\xa4\xc9G\x9dAKA\xa2L5\
\x92oddd\xab\xc0~g\x5c\xf6\x0d\x1b6\xec2\
\xa5\x96\x00\x82\xa4\x99\x913\x0a\x0a1E\xde\x84\xd4\xcc\
X\xc4\xa7\xfe\x86\x84\xb4\xb3H\xbc\x1d\x05\xadVKr\
v\xbb4W\x15\xcbQ\xab\xd3J\xa5***Be\
e%\xc4\x99\xb0}<\xfe\x89'O\x9eT\x92\x8c&\
\x84P=\xf7\xb1\x14I\xbd\xbe\x1e\x9a\xb2b\x84_\xff\
\x19%\xf510t\xa7\xa0\xb1/\x0d\xd5-q\xf8+\
\xc5\x07\xd99)\xa8\xd3\xd5I\xefj4\x1a\xa8\xd4\x0a\
\xa8\x0b\xd5(..Fuu5\x04v\x1e9\xfes\
\xe2\x89\xed\x91#\x946\x09\xa5\x9d\x8c\x80/3\x0a\xa6\
T\xacS\xbdiM\x87#\xe77BYw\x1a%\xad\
\xe7\xa0\xeb\x8f\x82\xfe\xc1%Tt\xfc\x81\x82\x86_q\
\xe2\xd2zA\x5c\x86\xf2\xf2\x0a\xa9Q\x15\xca<\x8e\x14\
#\x05Q[[\x0br\x88\xfb&\xb9\x5c\x9eCn\x0a\
X\x91\x97':X\xadFII\x89p.\xb7DL\
1\xeczf\xa1\xba\xaa\x1a\x97\xaf\x85 8\xcd\x09\x19\
\xb5\xbb\x91\xd3\xbc\x07\x8a6\x1f(\xdb}\x90{w\x1f\
\xb2\xf4?!<\x7f\x11\xceE\xee%\x0e\xf7<\xb2\xb3\
\xb3I\xce\x00\x88Il\xe2Q\x04\xf2\xf3\xf3!\xb8?\
\xa6\x80\x8dB\x0d\xeb\xccT\xf1\x05\xb3\x08\x0a\xb0\x88\xaa\
\xa9\xd6\xc2\xffw\x0f\x9cW\xccDh\xd12$h\xbf\
FR\xbd\x07\x92\x0c\x1e\xf8[\xe7\x8e\xb0\xd2U8\xa7\
t\x82\xef\xb9\xb5P\x15\xa8q\xe7\xce\x1d\xe4\xe4\xe40\
\x10f\x8fAP\x0c9$.r\x0a\xee\xcf(`\x93\
\xf8\xbc\xf2\xf8d\xba\xa8\xce,\x82}`\xb9\xaf\x16\x02\
|\x83\xbeBH\x9e3N\xcb\xe7\xe0x\xfe\x1c\x04\xaa\
\x5c\x11X\xe0\x8a\x00\xe5\x5c\x9cR|\x84`\xf9l\xec\
\x0dZ,\xa2W0\x03\x8c\x92e\x1cM\xceu\x8a\xa3\
\x80\xcf)`mrr2\x15Q\x849\x13t\xa0z\
\xf6\x00\xef\x05H%\x02\x82\xbeGP\xea\x0c\x9c\x92\xcd\
\xc5Q\xd9<\x1c\x92-\xc0A\xd9B\xf8\xc9\x84\x88l\
\x17\x04\xcbf\xe1\x07\xdfu(((\x14V@_\xf6\
\x12Gf\xd2BN\xae\x94\x94\x14\x0aXC\x01\xb3\x8e\
\x1d;\xd6\x1e\x1d\x1d=\x12\x1b\x1b\xfb\x0f\xb3`]\x0e\
\x8a\xe0=\xe7\xf1\x09\xd7\xe1\xf5\xcb\xbb\x08\xca\x9a\x83\xc3\
\xe9\xf3\xb0/m!~Lu\xc3\xfe\xb4\x05\xf0\xcf\xf8\
\x08\xde\xe1\xd3p2\xe8(\xfb\x89Fr\xfaY\x22'\
69\xc8%8\xdb\xc8=\xc1\xd6\xd6\xd6\x86\x13a+\
\x99\x92C\x87\x0e\x19\xa8\xd2\x5c\x0e\xa6P|\x09%\x00\
\xb5\xb8\xf7>\xe8\x85\xefN\xbc\x8fc\xb7f\xc3\xe7\x96\
+v'\xbb\xe1p\xaa\x10\x13:\x0d\xdbwmb\xea\
\x05y\x11\xb3\xc9\xb3\xc4B\xce\x8c\x08\xeczr\x90\x8b\
\x9c\x93'O&\xb7\xd5\xc5'+W\x1e\xa7\x83UO\
0\x0b\xe2>_\x02J\xcfH\xc3A\xbf=\xf8v\xe7\
Rx\xfa\xcc\xc06\xdf\xe9\xf0\xd8%2\xb1\xcf\x0bI\
\xc9\x89\xa2Wj\xe8\xc3\xc6\xb3\xae\xb9\xb4\xb6b\xc5\x8a\
\xe3/s\x14\xbb^\xbcx\xf1\x81Pl\xa43\x1d\xcb\
+\xcb\xa0P\xcb\x90\x9d\x95-uwrR\x12\x22\xa2\
\xc2\x10|6H\xd8i\xc4\x5c\x8eFn\x9e\x8c\xa4\xdc\
r\xdc=l`\xd6\x9d\xa5` \xc6\xd0\xd0\xd0\x07\xc4\
~\x19\x01\xb6\xde\xde\xde\x1a6\x0a;\x95\xc6]\x92)\
O\xc6-\xc5U\xdc\xbe\x9d\xce4\xf3\x84\x93\x1aU\xda\
\xb2\x15U\xect6\x9b\xf9\xf8f\xfa\xf9\xcc\xd2\x0f\x07\
\x0e\x1c\xd0\x10{<r\x9b\xfd\xfb\xf7\x9fa\xbdx\x88\
\x90\x98\x87\x14S\xc8\xba\x96jJ\xa07h\x19!\xc1\
\xcd\x8d\xca3\x83\xc76?\x5c\xec\x19\x9a\xf9\x9b`\xf9\
|\xf3]??\xbf3\xe4\x18\x93\xdd\xc5\xc5\xe5K:\
1\xfdt2\x9d\x8e\xe6H-\xdf\x05\xa6\xd9\xfa\xe3D\
B\x1e\xb3\xd6f}\x00\xb1\x8c,\x03\xb1\xe7\xcf\x9f\xff\
\xc5\x8b2\xb0L\xd4\xaa\x8f\xc4\xa6\xfa3\x02\xb3\x00#\
\xc1L\xc7*\x8d\x04c\x99\x91#E\xd3\x97\x18\xc4\x14\
\xbd\xd5K\x8e1\xd9\x1d\x1c\x1c8\xb8'$$\x0c\x0b\
'Fod4b\xcf\xf6\x84\x87\x87\x0f\xb2\xb6\xcf#\
\xb7^\xe3;\x11\x11\x11\x83\x81\x81\x81=\xa6o\x89\x91\
X\xe2G\xea0\xb1\xa7N\x1d\xe7\xaf\x83\xbd\xbd=\x7f\
\x82{\x84\x85\x85\xddgCm\xd9\xb2\x85?\xa3\x96\x0a\
[\xe5\xec\xec|A\xfcLo1\xa5w\xb4\xb1\xc6\xcd\
NNN\x17\xf8.}\xb6n\xdd\xaa\x22\x06\xb1\x889\
e\xca\x14b\x8f\x7f\xd9\xd9\xd9qX\xe7\xe8\xe8xB\
\x8c3\xad\x9bT\xd8\xa2+W\xae<\xb2\x16\xc19\xd7\
\xf8lT\x93\xcd$\x06\xb1\x88\xf9\xba\xae\xf7bbb\
\xbaX\x1a6\x15\x8ds\xae\xf1\xd9\x847p\xd9\x07\x07\
\x07k\xb9\x03\xf8\xb3\x9b\xc69\xd7\xf8\xec\x7fg\x9f4\
i\x92\x8d\xe9,\xdf)\xcc\x87\xc69\xd7\xf8]yU\
\xbc\x7f\x01\x06A\xf8\xf9\xb2\x87\xf2i\x00\x00\x00\x00I\
END\xaeB`\x82\
\x00\x00\x05\x08\ \x00\x00\x05\x08\
\x89\ \x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
@@ -1360,6 +1483,144 @@ $&\x93\xc8\x7f\xb3\xe7\xcc*\xc2w\xf1nHG\x09\
\xf9\x5c\x8a\xda\xed\x0c\xf6t\xf6\xe1\xf9-'\x97\x16\xdc\ \xf9\x5c\x8a\xda\xed\x0c\xf6t\xf6\xe1\xf9-'\x97\x16\xdc\
5\x03\xef$\x18\xdea\xfc\x07;\x00\xdf\x09\xb1\xdb|\ 5\x03\xef$\x18\xdea\xfc\x07;\x00\xdf\x09\xb1\xdb|\
\x84\x00\x00\x00\x00IEND\xaeB`\x82\ \x84\x00\x00\x00\x00IEND\xaeB`\x82\
\x00\x00\x08{\
\x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\
\x00\x00\x00\x09pHYs\x00\x00\x03v\x00\x00\x03v\
\x01}\xd5\x82\xcc\x00\x00\x00\x19tEXtSof\
tware\x00www.inksca\
pe.org\x9b\xee<\x1a\x00\x00\x07\xf8ID\
ATx\xda\xb5W\x0bX\xcdi\x1a\xff,\xd5\xa2\xc1\
\x8e\x8ad<\xe52.\xab\xc9\x0e\xe5t*a\x0c!\
\x95\xa2\x9abZ\x97U\xf3<\x96M3I5\x83\xd5\
E24\x88RjY\xc3&\x0c]\xb6D\xd3\x95t\
t\x8fD[\x9d\xee\xb7\xa3\x9e\xd2)\xba\xfd\xf6\xfdR\
\xf3\xa4Q\x1d\xb3\xcf~\xcf\xf3{\xce9\xdf\xff\xfd\xfe\
\xef\xef{\xef\x87\x01\xf8\xbf\x80\xd6(\x82\x02\xe1\x03\xc2\
x\xc2\x18\xbe7X\xae_x\xf4)\x7f\xff\xd5\xf49\
\x8e\x0b\xfd\x8f\x8a\x7f\xa7\xac\xac\xac\xe8\xe5\xe5e\x1b\x15\
\x1d\xf5$;;\xbb;//\xefUVvV\xd1\xd5\
\xabWWr]o\xcb\xd3\xc6\x95+W\xdc_\xbe|\
\x89R\xb1\xf8UXx\xf8~\xbe\xf7[o=g\xce\
\x1c\xe5\x90\x90\x90\xc2\xfc\xc7\xf9(+/Cuu5\
jkk!\x91HPRZ\xd2q\xe8\xd0\xa1\xf5\x03\
/\xc9\xd7\xa4\xe2\xe2\xe2\xd7\xaf;:P]S\x83\xb4\
\x87\x0f{\xec\xec\xec\xcc\xf8M~\x03\x81\xdf\xef\xdd\xbb\
\xf7\x87\xb4\x87i\xa8\xac\xaaD\x83\xa4\x01\x12Btt\
\xf4kWW\xd7\xeaE\x8b\x16]TTT\x9c\xcf\xdd\
\xf1\x16\x813g\xce\xdc\xae\xac\xac\x84\xb8\xbc\x1c\xa2G\
\x8f`\xef\xe0\xd0\xbax\xf1\xe2\x19\xef\xeb\x0eZ\x93\x8f\
\x1f\xf7-\xab\xa8(\xefU\xdc\xda\xda\xca-\xd0\xad\xa4\
\xa4\xf4\x1d=[F\x98\xceI\x0e\xb6\xc0\x18\xc2\xbc\x8b\
\x97.\x15\xdf\x89\x8b\xebIJNF[[\x1b6[\
Z\xa5\xd3\xfe\x04YIp9\x15\x15\x95YQQ\x91\
\xdd\x9c@SS\x13\xda\xdb\xdbPZZ\xfa\x8a\xbf\x9f\
\xa00\x5c\x10\xf2\x87\x0bW\xafYs\xd6v\xeb\x97\xad\
\x05\x05\x05\x90\x12\x89\x8d\xe6\x16Q\xea\xea\xea\x93d!\
AK\xce\xd2\xd2\xd2.%%\x19999\x90\xbc\x90\
\xf4\x92\xa8\xab\xaf\x83\xa1\xa1\xe1\x12\xee\xd2\x01\xd91\xa6\
?+\xdez\x01a\x9a\xbc\xbc\xbc\xf9\xe7\xab\xd7\x14<\
\x7f\xfe\xbc\xd7\x84\xebML\x13\x96-[\xa6<\x12\x09\
Z\x8a\xbbw\xef\xfe\xd7\xdd{q\xc8\xc8\xc8@Uu\
\x15\xaa\x09\x15\x15\x15\xb8r\xf5\xc7V\x1b\x1b\x9b5\x5c\
\xe6\xb0\xc7\xe1\x8d\xc1\xc1A\x05_\xd1\xe2:\x7f\x95B\
\x84\x89\xdc_\x02]afIi)\x9a\x9b\x9bac\
\xfbe\xba\x85\x85\xc5\xd4\xe1\x02\x93\xd6\x94s\x01\xe7Z\
\x92\x92\x93\xb8\xd9\xf1 \xed\x01\x0a\x0b\x0b\xf1\xecY!\
\x92S\x92p\xd2\xef\xfb\xce\xd8;\xb1\x92\xd4\xd4T\xf8\
\xfa\x1ek\xd7\xd1\xd1\xb1\xe1\xf10T\x01\x19O\x10\x0a\
\x04\xba\xa9t\x032c=\x1c\xbe\xda\x9d\xbfk\xd7\xae\
\x19\xef\x22\xc1\xd3\xd6\xca\xca\xca\xfa\xe6O7\x10p>\
\x08!\xff\x08\x81\xe7QO\xf8\xf8\xfa\x22+;\x93\x02\
;\x1d!\xa1!\xe0\xca\xc3\xc2\xc2(\xc8\xed\xcb\xe9\xcc\
,\xfe\xae\xe1\xaa\xd88\x82\xb6\xbe\xbe\xc1\xdd\xba\xbaz\
TP\x968\xefw)rvv\x9e3\x98\x04\x0f\xd6\
\xc3\x87\x0f%\xe4\xe5\xe5b\x87\xbd=\x5c\x5c\xbf\x81(\
\xe3!\xf4\xf4\x84\xa4\x5c\x84;q\xb1\x08%R\x91\x91\
\x11\xf0\xf4\xf4\xe8\xa6Z\xf15?\xc3\xcf\x8eTJ\xc7\
\x12\xfedh\xb8<\xb2\xb1\xb1\xb1\xa7\xb8\xa4\x04\x01\x81\
\x81\xe5TL\x16\x0e\x08*y\xf2\xfd\x0e\xba]\x17\xcf\
\x9e\xa0\xe0\x0b\xd8ha\x86\xfc\xc7\xb9ptr\xc4\x8d\
\x9b7@\xae\x81\xb7\xb7\x17\xdc\xbfu\xeb\xd6\xd4\xd4\xfc\
\x91\xceh\xf4\x17;Y\xeb\xb9\xe6\x8a\x15+\xc3ZZ\
Z\xba\x9f\x17\x15!]$\xaa;q\xe2\xc4\x12n%\
r\x8b3/\xb7\xe1\xe1\xd7\x90\x90\x90\x80\xb3g\xcf\xc2\
\xda\xfa\x0bxy{\x83d\xe0\xe6\xee\xd6\xe3\xe8\xf8\xb7\
\xd7\xdb\xb6o\xab\xa7\x9b\x1f\xa33\xdc\x82r\xfd:d\
-0\xf2\x84\x05\xe6\xe6\xe6\x17\xa5Ri\xd7\xe3'O\
@\xf5\xbd\xe9\xe0\xc1\x83\x17sss{\xbc\xbc<\xb1\
b\xe5r\x1c8\xe0\x82\xd3\xa7O\xc3\xc5\xc5\xa5\xd5\xd6\
\xd6\xf6\xa9\x96\x96\xd69:gM\xd0%\xcc$|\xd8\
_\x05e%08M\xe7n\xd9\xb2%\x80\xfaFg\
Nn.'\x01\x0fO\x0f,_n\x88\xcd\x9b7\xe1\
\xf2\xe5\xcb \xc5i$\xb7\x81\xf01aJ_7\x94\
\x1f*\x8d\x07m\xc8d\x89\x85\xdb\xb7o\x7f\xc2\x8bL\
Ff\x16\xe5|&v\xd9\xef\x02u:888\xe4\
\xd3s\x01W*k/y\xdf\xfe\xae\xe8\xed\xed}\xf3\
\xde\xbd{\xe4g+P\xff\xa0xx\xd4K\xc2\xc7\xc7\
\xa7!1\xd4\xf2N\x84\xbb\xfaw\x03\xeb\xfdH\x90Y\
\xf9$Z\xd4\xb4\xe2\xf2\xf3\xf3\xb1u\xab-\x96\x0a\xb4\
\xa1o\xa0\xcf\x8b\x0e\xa5Z\x06\xca\xf2\xfcz\xa4e!\
(\xba\xa0\x88\xa0\x1dl_\x7f\xa0\x8d\x04\x99\x06\x0c\x0a\
&\x15\xea\xf1\xf7333a\xfd\x85\x15\x0c\x0c\xf5\xa1\
\xa5\xf5\x09\x8e\x1d;\x06rG\x9b\xf8\xd1\xdf;:\xcb\
>D\xdb\xd3y\x90\xde\x1c\x0f\x91\x1fk\xf8h2S\
\x93\xc5\x0a#*\x17\x08\x04j\xd7\xae]\xcbNII\
\xc1\xa6M\xe6tk=\xcc\x9f?\xbf7\xc5N\x9e<\
\xd9\xeaj16\x5c|\x95u7\xddg\xe8\x8egx\
\x95\xccP\x13\xcb\xf0\xef#,\xf2\xaf\x9f\xb1\x05\xdc\x12\
\xc3\x11\x19V\xf9\xaaU\xabf\xd20\xf1466\x96\
:\xa3\x19\xf4\xf4u1k\xf6LP\xfa!((\xa8\
\x9d\x88\xec\xfb\x5c\x8b9\xdc\xfe\x9a\xb5\xd7]fh\x89\
c\xe8,a\x90\x1604g04\x8a\x14\xba\x13}\
\x14\xc3\x94\x95\x99\xe2P$\x86Tnjj\xba >\
>\xbe4<<\x1c\xa6f&\x10\xea\x090c\xc6G\
\xd8\xb3g\x0fO\xb7\x0e===\x1elj\x04\xd5y\
j\xcc\xde\xc7\x96\x15\xfd\xec3\xaaK\x92B$\xc4\x0c\
]\xe5\xa3\xd1.6AQ(C\xe4>\x16MM\xfd\
\x9dm\x9d\xbd\xab\xb1P_\xff\x94JkuH\xc8\x05\
l05\x86@\xb8\x14jj\xd3(\xf8\xb6\xe2\xfa\xf5\
\xeb]FFF\xa7Hn\x06\x97\xed\xeb\xa0\xfc\xe5\x9a\
\x1fOc.\x0fN\xb2\xce\x17\x0f\x88D\xc5\x04t\xd4\
\xedEC\xb2\x12\x8a\x7f\xea%\xf1d\xc9\x5c\xa64\x98\
\xc4\xaf\x94S\xa11\x10\x89D\x12\xbf\x1f\xfc`l\xb2\
\x9e\xa2]\x07SU\xa7\xc2\xd8\xd8\x18QQQ=D\
\xee2\xefd\x83+\x1a\xffM\x98\xe9m\xcd\xd2\x8b\xc9\
\x1d\x0d\x89\x14\x0f\xc5c\xd1Qk\x0bi\x8e\x12\xc41\
\x14\x17\xfbX\xa9\xd9\xa7Lu`\x8dxK9\xd5\xf5\
\xd5T\xd7\x9b=<<\xb0~\xc3:\xe8,]\xd2\xab\
\xdc\xc0\xc0\x00111=;w\xee\x8cf\xecM`\
\x0d5\x94N\x99\xc8V\x86\xda\xb3\x17\xff\xf9'C9\
\xdd\x5c\x92\x22\x87\x8ej#H\xb3'\xa3\xe2.C\xcc\
7\xac\xc6Z\xc0\xd4\xfbI\xfc\xa2\x9c\xa6Y3\x1a\xc5\
Z\x1f\xd1Pz\xfc\xfb\xe3\x10\x0au1e\x8a\x0ah\
\x92\x05\x0fB''\xa7\xfb\xbc3\x12\x14F(V\x13\
\xc7)0\x93\xf3;Y}m\xfc\x1b\x12\xf5?\x8fF\
G\x956\xa4Y\x7f@u\x02\xc3\xdd\x03\xec\x85\xfb\x06\
\xf6GN\xa2\xf7\x90\x9a\x9a\xdat\xba\xb9\xd4\xd3\xd3\xf3\
\xd9\xbau\xeb\x22)\xbd\x1a]\x5c\xf6\x83\xba\x17\x22\x22\
\x22@\xed\x97\x97X\xa1\x0c\x7f\x5c\xfaILR\x18\xc3\
V\xfb\xff\x99U\xd5\x10\x89\xca\xdb\x94\x9a1\xa3\xd0Y\
9\x1b\xd2LE\xd4\xa52dy\xb3\x12^Y\x09L\
\xde\xcd\xcd\xcd\x8e\x22\xbe\x89\xbe/'\xcc^\xbbv\xad\
3\x99\x9c\xfb\x9c\xe7{)\xed}F\xf8\xe0}&\xe4\
\xbe\x89z\x85\xdf\x16VR\x15K$\x22\xde\xa0\xb3\x5c\
\x09\xcd)\x0a\x90\x5cb\xed}\xcd\x8a\x8d\xa5?\x0d\xdf\
\xc6\xc5\xc5\xb5ihh\xcc\xe5&vtt\xdc\x96\x98\
\x98\xd8\x13\x18\x18X\xab\xa0\xa0`\xc2\xd8\xe0\x14\x92\xbd\
w\x10\xf4\x8fnf\x85\xe2(\x86\xaah\x06\xa9\x88\xa1\
=i4\xe2\x8f\xb2\x87\xf4L\xa9\xd7\x02rrr\xda\
4\xab\xb5\xdf\xbau\xab\x90>\x03(\xd5jN\x9d:\
\xd58a\xc2\x04[.$cg\x1bn\xbe\x14\x1c1\
g9\xf9~\x0c\xe2\xf3\xac'\xdd\x8fU\xd0\xdeb\xae\
\xbb_HE(\x14\xfa\x04\x07\x07\xb7\xf8\xfb\xfb\xb7\x91\
\xcf\xc5\xaa\xaa\xaa\x7f\xe1&\x92Q\xb9,\xa3\x9d\xa6\xb6\
\x06s\xb2\xd6cG\xe8\xfb'}{\xa3~\xc9\x02\xc2\
t\x82\x11a\x13a)a\xb2L\xcae'!\xc7]\
\xc91\xb0?\xfc\x17\xb0l\xc1\xc4\x9c\xd3\xd1\xfc\x00\x00\
\x00\x00IEND\xaeB`\x82\
\x00\x00\x04+\ \x00\x00\x04+\
\x89\ \x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
@@ -2271,6 +2532,10 @@ qt_resource_name = b"\
\x00z\ \x00z\
\x00o\x00o\x00m\x00-\x00o\x00r\x00i\x00g\x00i\x00n\x00a\x00l\x00.\x00p\x00n\x00g\ \x00o\x00o\x00m\x00-\x00o\x00r\x00i\x00g\x00i\x00n\x00a\x00l\x00.\x00p\x00n\x00g\
\ \
\x00\x0d\
\x0f\xa5%\xc7\
\x00r\
\x00u\x00n\x00-\x00b\x00u\x00i\x00l\x00d\x00.\x00p\x00n\x00g\
\x00\x0f\ \x00\x0f\
\x0bb\xb8g\ \x0bb\xb8g\
\x00d\ \x00d\
@@ -2327,6 +2592,11 @@ qt_resource_name = b"\
\x00e\ \x00e\
\x00d\x00i\x00t\x00-\x00r\x00e\x00d\x00o\x00.\x00p\x00n\x00g\ \x00d\x00i\x00t\x00-\x00r\x00e\x00d\x00o\x00.\x00p\x00n\x00g\
\x00\x16\ \x00\x16\
\x01p\xe1\x87\
\x00p\
\x00r\x00e\x00f\x00e\x00r\x00e\x00n\x00c\x00e\x00s\x00-\x00s\x00y\x00s\x00t\x00e\
\x00m\x00.\x00p\x00n\x00g\
\x00\x16\
\x0c\xb7\x89\xc7\ \x0c\xb7\x89\xc7\
\x00d\ \x00d\
\x00r\x00a\x00w\x00-\x00b\x00e\x00z\x00i\x00e\x00r\x00-\x00c\x00u\x00r\x00v\x00e\ \x00r\x00a\x00w\x00-\x00b\x00e\x00z\x00i\x00e\x00r\x00-\x00c\x00u\x00r\x00v\x00e\
@@ -2383,59 +2653,63 @@ qt_resource_struct = b"\
\x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
\x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x1a\x00\x00\x00\x03\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x1c\x00\x00\x00\x03\
\x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x01\xde\x00\x00\x00\x00\x00\x01\x00\x00G\x5c\ \x00\x00\x01\xfe\x00\x00\x00\x00\x00\x01\x00\x00N\xe8\
\x00\x00\x01\x9f{C\xf1'\ \x00\x00\x01\x9f{C\xf1'\
\x00\x00\x02\x9a\x00\x00\x00\x00\x00\x01\x00\x00dK\ \x00\x00\x02>\x00\x00\x00\x00\x00\x01\x00\x00Z\x1a\
\x00\x00\x01\x9f\x7f\xa8\xa8)\
\x00\x00\x02\xec\x00\x00\x00\x00\x00\x01\x00\x00tV\
\x00\x00\x01\x9f{0\xc99\ \x00\x00\x01\x9f{0\xc99\
\x00\x00\x01\xa4\x00\x00\x00\x00\x00\x01\x00\x00<\xa6\ \x00\x00\x01\xc4\x00\x00\x00\x00\x00\x01\x00\x00D2\
\x00\x00\x01\x9f\x7f&\x83\xcd\ \x00\x00\x01\x9f\x7f&\x83\xcd\
\x00\x00\x01\x84\x00\x00\x00\x00\x00\x01\x00\x004\xbe\ \x00\x00\x01\xa4\x00\x00\x00\x00\x00\x01\x00\x00<J\
\x00\x00\x01\x9f{C\xf1\x18\ \x00\x00\x01\x9f{C\xf1\x18\
\x00\x00\x03R\x00\x00\x00\x00\x00\x01\x00\x00}\x9f\ \x00\x00\x03\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x8d\xaa\
\x00\x00\x01\x9f\x7fY\xceg\ \x00\x00\x01\x9f\x7fY\xceg\
\x00\x00\x02P\x00\x00\x00\x00\x00\x01\x00\x00V\xbd\ \x00\x00\x02\xa2\x00\x00\x00\x00\x00\x01\x00\x00f\xc8\
\x00\x00\x01\x9f\x7fV\xd5\xc0\ \x00\x00\x01\x9f\x7fV\xd5\xc0\
\x00\x00\x03\x08\x00\x00\x00\x00\x00\x01\x00\x00t\xba\ \x00\x00\x03Z\x00\x00\x00\x00\x00\x01\x00\x00\x84\xc5\
\x00\x00\x01\x9f\x7f&\x83r\ \x00\x00\x01\x9f\x7f&\x83r\
\x00\x00\x02|\x00\x00\x00\x00\x00\x01\x00\x00]P\ \x00\x00\x02\xce\x00\x00\x00\x00\x00\x01\x00\x00m[\
\x00\x00\x01\x9f\x7f&\x83\xb1\ \x00\x00\x01\x9f\x7f&\x83\xb1\
\x00\x00\x00\xfc\x00\x00\x00\x00\x00\x01\x00\x00$|\ \x00\x00\x01\x1c\x00\x00\x00\x00\x00\x01\x00\x00,\x08\
\x00\x00\x01\x9f\x7fY\xce\x82\ \x00\x00\x01\x9f\x7fY\xce\x82\
\x00\x00\x01\xc0\x00\x00\x00\x00\x00\x01\x00\x00C\xdc\ \x00\x00\x01\xe0\x00\x00\x00\x00\x00\x01\x00\x00Kh\
\x00\x00\x01\x9f{C\xf1.\ \x00\x00\x01\x9f{C\xf1.\
\x00\x00\x01d\x00\x00\x00\x00\x00\x01\x00\x00/\x10\ \x00\x00\x01\x84\x00\x00\x00\x00\x00\x01\x00\x006\x9c\
\x00\x00\x01\x9f{{\xa5\xd5\ \x00\x00\x01\x9f{{\xa5\xd5\
\x00\x00\x03x\x00\x00\x00\x00\x00\x01\x00\x00\x81\xdf\ \x00\x00\x03\xca\x00\x00\x00\x00\x00\x01\x00\x00\x91\xea\
\x00\x00\x01\x9f\x7f&\x83\x10\ \x00\x00\x01\x9f\x7f&\x83\x10\
\x00\x00\x00\xde\x00\x00\x00\x00\x00\x01\x00\x00 \xd9\ \x00\x00\x00\xfe\x00\x00\x00\x00\x00\x01\x00\x00(e\
\x00\x00\x01\x9f\x7f&\x83~\ \x00\x00\x01\x9f\x7f&\x83~\
\x00\x00\x02\xe4\x00\x00\x00\x00\x00\x01\x00\x00p\xd7\ \x00\x00\x036\x00\x00\x00\x00\x00\x01\x00\x00\x80\xe2\
\x00\x00\x01\x9f\x7f&\x83T\ \x00\x00\x01\x9f\x7f&\x83T\
\x00\x00\x00^\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xc1\ \x00\x00\x00~\x00\x00\x00\x00\x00\x01\x00\x00\x14M\
\x00\x00\x01\x9f\x7fY\xce^\ \x00\x00\x01\x9f\x7fY\xce^\
\x00\x00\x00\x82\x00\x00\x00\x00\x00\x01\x00\x00\x11\xcd\ \x00\x00\x00\xa2\x00\x00\x00\x00\x00\x01\x00\x00\x19Y\
\x00\x00\x01\x9f{0\xc9B\ \x00\x00\x01\x9f{0\xc9B\
\x00\x00\x01D\x00\x00\x00\x00\x00\x01\x00\x00,\xb6\ \x00\x00\x01d\x00\x00\x00\x00\x00\x01\x00\x004B\
\x00\x00\x01\x9f\x7f&\x83)\ \x00\x00\x01\x9f\x7f&\x83)\
\x00\x00\x02\xc2\x00\x00\x00\x00\x00\x01\x00\x00kU\ \x00\x00\x03\x14\x00\x00\x00\x00\x00\x01\x00\x00{`\
\x00\x00\x01\x9f{C\xf1N\ \x00\x00\x01\x9f{C\xf1N\
\x00\x00\x02\x1e\x00\x00\x00\x00\x00\x01\x00\x00R\x8e\ \x00\x00\x02p\x00\x00\x00\x00\x00\x01\x00\x00b\x99\
\x00\x00\x01\x9f\x7f&\x82\xf2\ \x00\x00\x01\x9f\x7f&\x82\xf2\
\x00\x00\x00\xb0\x00\x00\x00\x00\x00\x01\x00\x00\x1a9\ \x00\x00\x00\xd0\x00\x00\x00\x00\x00\x01\x00\x00!\xc5\
\x00\x00\x01\x9f{\x8d\xf34\ \x00\x00\x01\x9f{\x8d\xf34\
\x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ \x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
\x00\x00\x01\x9f{0\xc9+\ \x00\x00\x01\x9f{0\xc9+\
\x00\x00\x01\xfe\x00\x00\x00\x00\x00\x01\x00\x00J\xbc\ \x00\x00\x02\x1e\x00\x00\x00\x00\x00\x01\x00\x00RH\
\x00\x00\x01\x9f{C\xf1=\ \x00\x00\x01\x9f{C\xf1=\
\x00\x00\x006\x00\x00\x00\x00\x00\x01\x00\x00\x05\x86\ \x00\x00\x006\x00\x00\x00\x00\x00\x01\x00\x00\x05\x86\
\x00\x00\x01\x9f\x7f&\x83\x99\ \x00\x00\x01\x9f\x7f&\x83\x99\
\x00\x00\x03\xa2\x00\x00\x00\x00\x00\x01\x00\x00\x84u\ \x00\x00\x03\xf4\x00\x00\x00\x00\x00\x01\x00\x00\x94\x80\
\x00\x00\x01\x9f{{\xa5\xe3\ \x00\x00\x01\x9f{{\xa5\xe3\
\x00\x00\x01$\x00\x00\x00\x00\x00\x01\x00\x00'\xef\ \x00\x00\x00^\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xc1\
\x00\x00\x01\x9f\x7f\xac\xf2\xc6\
\x00\x00\x01D\x00\x00\x00\x00\x00\x01\x00\x00/{\
\x00\x00\x01\x9f\x7fV\xd5\xe5\ \x00\x00\x01\x9f\x7fV\xd5\xe5\
\x00\x00\x03*\x00\x00\x00\x00\x00\x01\x00\x00x\xac\ \x00\x00\x03|\x00\x00\x00\x00\x00\x01\x00\x00\x88\xb7\
\x00\x00\x01\x9f{0\xc9R\ \x00\x00\x01\x9f{0\xc9R\
" "

View File

@@ -0,0 +1,9 @@
import logging
LOGGER_NAME = "bedit"
def get_logger(name: str | None = None) -> logging.Logger:
"""Return the shared application logger, optionally scoped to a module."""
return logging.getLogger(LOGGER_NAME if name is None else f"{LOGGER_NAME}.{name}")

View File

@@ -1,8 +1,8 @@
import json
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from bedit.core.model import GraphDocument from bedit.core.model import GraphDocument
from bedit.core.serializer import DocumentSerializer
@dataclass(frozen=True) @dataclass(frozen=True)
@@ -13,7 +13,7 @@ class LibraryDocument:
def bundled_library_path() -> Path: def bundled_library_path() -> Path:
return Path(__file__).resolve().parents[1] / "data" / "libraries" / "default.json" return Path(__file__).resolve().parents[1] / "data" / "libraries" / "default.beb"
def default_library_paths() -> list[str]: def default_library_paths() -> list[str]:
@@ -21,12 +21,12 @@ def default_library_paths() -> list[str]:
def load_library_file(path: Path) -> LibraryDocument: def load_library_file(path: Path) -> LibraryDocument:
with path.open(encoding="utf-8") as file: document = DocumentSerializer.load(path)
data = json.load(file)
document = GraphDocument.from_dict(data)
name = str(document.metadata.get("name") or path.stem) name = str(document.metadata.get("name") or path.stem)
return LibraryDocument(name, document, str(path)) return LibraryDocument(name, document, str(path))
def library_candidates(path: Path) -> list[Path]: def library_candidates(path: Path) -> list[Path]:
return sorted(path.glob("*.json")) if path.is_dir() else [path] if not path.is_dir():
return [path]
return sorted((*path.glob("*.json"), *path.glob("*.beb")))

View File

@@ -255,6 +255,7 @@ class Graph:
connections: dict[str, Connection] = field(default_factory=dict) connections: dict[str, Connection] = field(default_factory=dict)
annotations: dict[str, Annotation] = field(default_factory=dict) annotations: dict[str, Annotation] = field(default_factory=dict)
junctions: dict[str, Junction] = field(default_factory=dict) junctions: dict[str, Junction] = field(default_factory=dict)
simulation_settings: dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> dict[str, Any]: def to_dict(self) -> dict[str, Any]:
return { return {
@@ -262,6 +263,7 @@ class Graph:
"connections": [connection.to_dict() for connection in self.connections.values()], "connections": [connection.to_dict() for connection in self.connections.values()],
"annotations": [item.to_dict() for item in self.annotations.values()], "annotations": [item.to_dict() for item in self.annotations.values()],
"junctions": [junction.to_dict() for junction in self.junctions.values()], "junctions": [junction.to_dict() for junction in self.junctions.values()],
"simulation": deepcopy(self.simulation_settings),
} }
@classmethod @classmethod
@@ -286,6 +288,7 @@ class Graph:
connections={connection.id: connection for connection in connections}, connections={connection.id: connection for connection in connections},
annotations={item.id: item for item in annotations}, annotations={item.id: item for item in annotations},
junctions={junction.id: junction for junction in junctions}, junctions={junction.id: junction for junction in junctions},
simulation_settings=deepcopy(data.get("simulation", {})),
) )
@@ -578,6 +581,7 @@ def clone_component(source: Component) -> Component:
) )
for junction in current.graph.junctions.values() for junction in current.graph.junctions.values()
}, },
simulation_settings=deepcopy(current.graph.simulation_settings),
) )
return Component( return Component(
id=str(uuid4()), id=str(uuid4()),

View File

@@ -1,6 +1,9 @@
import json import json
import zlib
from pathlib import Path from pathlib import Path
import msgpack
from bedit.core.model import GraphDocument from bedit.core.model import GraphDocument
@@ -20,3 +23,54 @@ class JsonDocumentSerializer:
json.dump(document.to_dict(), file, indent=2) json.dump(document.to_dict(), file, indent=2)
file.write("\n") file.write("\n")
temporary_path.replace(path) temporary_path.replace(path)
class BebDocumentSerializer:
"""Compressed MessagePack serializer for the binary .beb format."""
MAGIC = b"BEB\x00"
VERSION = 1
@classmethod
def load(cls, path: Path) -> GraphDocument:
payload = path.read_bytes()
header = cls.MAGIC + bytes([cls.VERSION])
if not payload.startswith(header):
raise ValueError("This is not a supported BEdit binary document")
try:
data = msgpack.unpackb(zlib.decompress(payload[len(header) :]), raw=False)
except (ValueError, zlib.error, msgpack.exceptions.MsgpackException) as error:
raise ValueError("The BEdit binary document is damaged") from error
if not isinstance(data, dict):
raise ValueError("The BEdit binary document has an invalid root value")
return GraphDocument.from_dict(data)
@classmethod
def save(cls, document: GraphDocument, path: Path) -> None:
packed = msgpack.packb(document.to_dict(), use_bin_type=True)
payload = cls.MAGIC + bytes([cls.VERSION]) + zlib.compress(packed, level=9)
temporary_path = path.with_suffix(path.suffix + ".tmp")
temporary_path.write_bytes(payload)
temporary_path.replace(path)
class DocumentSerializer:
"""Select the document serializer from its file extension."""
@staticmethod
def load(path: Path) -> GraphDocument:
serializer = (
BebDocumentSerializer
if path.suffix.lower() == ".beb"
else JsonDocumentSerializer
)
return serializer.load(path)
@staticmethod
def save(document: GraphDocument, path: Path) -> None:
serializer = (
BebDocumentSerializer
if path.suffix.lower() == ".beb"
else JsonDocumentSerializer
)
serializer.save(document, path)

View File

@@ -0,0 +1,3 @@
from bedit.core.simulation.service import Simulation
__all__ = ["Simulation"]

View File

@@ -0,0 +1,18 @@
from copy import deepcopy
from typing import Any
class Simulation:
"""Application-owned simulation service and state container."""
def __init__(self) -> None:
self.state: dict[str, Any] = {}
self.last_compilation_input: dict[str, Any] | None = None
def compile(self, graph: dict[str, Any]) -> None:
"""Compile a graph definition.
This is intentionally a stub. Keeping a copy of the input makes the
service useful for incremental compiler development and UI inspection.
"""
self.last_compilation_input = deepcopy(graph)

Binary file not shown.

View File

@@ -1,435 +0,0 @@
{
"format": "bedit-document",
"version": 1,
"metadata": {
"name": "Untitled"
},
"roots": [
{
"id": "7ad651ef-6f76-4064-81d3-3fd6dd4d2918",
"name": "gain",
"position": {
"x": 0.0,
"y": 0.0
},
"rotation": 0.0,
"interface": {
"inputs": [
{
"id": "port-df34ce84",
"name": "in",
"position": {
"x": 0.0,
"y": 0.0
},
"properties": {
"iconPosition": {
"x": 64.0,
"y": 64.0
}
},
"type": "signal",
"multipleConnections": false
}
],
"outputs": [
{
"id": "port-fe9e6486",
"name": "out",
"position": {
"x": 0.0,
"y": 0.0
},
"properties": {
"iconPosition": {
"x": 88.0,
"y": 40.0
}
},
"type": "signal",
"multipleConnections": false
}
]
},
"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": "K",
"color": "#00007f",
"fontSize": 24.0,
"lineStyle": "solid",
"lineWidth": 1.5,
"stroke": "#00007f",
"fill": "#ffffff"
}
]
},
"properties": {
"showName": false
},
"library": {
"showSubtree": true
},
"implementation": {
"kind": "text",
"source": {
"equations": "out = k*in;",
"parameters": [
{
"id": "parameter-61a43a86",
"name": "k",
"type": "real",
"value": "1"
}
]
}
}
},
{
"id": "12b21b11-8828-4a90-a561-19491dc9632e",
"name": "differentiate",
"position": {
"x": 0.0,
"y": 0.0
},
"rotation": 0.0,
"interface": {
"inputs": [
{
"id": "port-1cbabc8f",
"name": "in",
"position": {
"x": 0.0,
"y": 0.0
},
"properties": {
"iconPosition": {
"x": 64.0,
"y": 64.0
}
},
"type": "signal",
"multipleConnections": false
}
],
"outputs": [
{
"id": "port-4eeea4e7",
"name": "out",
"position": {
"x": 0.0,
"y": 0.0
},
"properties": {
"iconPosition": {
"x": 88.0,
"y": 40.0
}
},
"type": "signal",
"multipleConnections": false
}
]
},
"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": "d/dt",
"color": "#00007f",
"fontSize": 18.0,
"lineStyle": "solid",
"lineWidth": 1.5,
"stroke": "#00007f",
"fill": "#ffffff"
}
]
},
"properties": {
"showName": false
},
"library": {
"showSubtree": true
},
"implementation": {
"kind": "text",
"source": {
"equations": "initial out = initial;\nout = der(in);",
"parameters": [
{
"id": "parameter-331e38be",
"name": "initial",
"type": "real",
"value": "0"
}
]
}
}
},
{
"id": "afdc7696-622b-47e4-b2c3-28ec60786bf2",
"name": "integrate",
"position": {
"x": 0.0,
"y": 0.0
},
"rotation": 0.0,
"interface": {
"inputs": [
{
"id": "port-12903207",
"name": "in",
"position": {
"x": 0.0,
"y": 0.0
},
"properties": {
"iconPosition": {
"x": 64.0,
"y": 64.0
}
},
"type": "signal",
"multipleConnections": false
}
],
"outputs": [
{
"id": "port-b3370b1a",
"name": "out",
"position": {
"x": 0.0,
"y": 0.0
},
"properties": {
"iconPosition": {
"x": 88.0,
"y": 40.0
}
},
"type": "signal",
"multipleConnections": false
}
]
},
"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": 64.0,
"y": 40.0,
"width": 28.0,
"height": 48.0,
"text": "dt",
"color": "#00007f",
"fontSize": 18.0,
"lineStyle": "solid",
"lineWidth": 1.5,
"stroke": "#00007f",
"fill": "#ffffff"
},
{
"type": "text",
"x": 40.0,
"y": 32.0,
"width": 28.0,
"height": 54.0,
"fill": "none",
"stroke": "#00007f",
"lineWidth": 1.5,
"lineStyle": "none",
"text": "\u222b",
"fontSize": 32.0,
"color": "#00007f"
}
]
},
"properties": {
"showName": false
},
"library": {
"showSubtree": true
},
"implementation": {
"kind": "text",
"source": {
"equations": "initial out = initial;\nder(out) = in;",
"parameters": [
{
"id": "parameter-6bdc1c76",
"name": "initial",
"type": "real",
"value": "0"
}
]
}
}
},
{
"id": "f6b8e5c9-4d13-4f7b-b574-e00ca96e42c3",
"name": "add",
"position": {
"x": 0.0,
"y": 0.0
},
"rotation": 0.0,
"interface": {
"inputs": [
{
"id": "port-c995845c",
"name": "in",
"position": {
"x": 0.0,
"y": 0.0
},
"properties": {
"iconPosition": {
"x": 64.0,
"y": 64.0
}
},
"type": "signal",
"multipleConnections": true
}
],
"outputs": [
{
"id": "port-a2f0dea6",
"name": "out",
"position": {
"x": 0.0,
"y": 0.0
},
"properties": {
"iconPosition": {
"x": 80.0,
"y": 48.0
}
},
"type": "signal",
"multipleConnections": false
}
]
},
"icon": {
"shape": "rectangle",
"fill": "#f4f4f4",
"border": "#303030",
"text": "Text",
"size": {
"width": 128.0,
"height": 128.0
},
"elements": [
{
"type": "circle",
"x": 40.0,
"y": 40.0,
"width": 48.0,
"height": 48.0,
"fill": "#f4f4f4",
"stroke": "#303030",
"lineWidth": 1.5,
"lineStyle": "solid"
},
{
"type": "text",
"x": 48.0,
"y": 48.0,
"width": 32.0,
"height": 32.0,
"fill": "none",
"stroke": "#00007f",
"lineWidth": 1.5,
"lineStyle": "none",
"text": "+",
"fontSize": 18.0,
"color": "#00007f"
}
]
},
"properties": {
"showName": false
},
"library": {
"showSubtree": true
},
"implementation": {
"kind": "text",
"source": {
"equations": "out = sum(in[i] for i in 1:in.N);",
"parameters": []
}
}
}
]
}

View File

@@ -0,0 +1,26 @@
import logging
from PySide6.QtCore import QObject, Signal
from PySide6.QtWidgets import QPlainTextEdit
class _LogEmitter(QObject):
lineReady = Signal(str)
class ApplicationLogHandler(logging.Handler):
"""Thread-safe bridge from Python logging into a Qt text view."""
def __init__(self, output: QPlainTextEdit) -> None:
super().__init__()
self.emitter = _LogEmitter(output)
self.emitter.lineReady.connect(output.appendPlainText)
self.setFormatter(
logging.Formatter("%(asctime)s %(levelname)s %(message)s", "%H:%M:%S")
)
def emit(self, record: logging.LogRecord) -> None:
try:
self.emitter.lineReady.emit(self.format(record))
except Exception:
self.handleError(record)

View File

@@ -137,6 +137,19 @@ class EditGraphItemCommand(QUndoCommand):
self.controller._set_graph_item_data(self.owner_id, self.item_kind, self.item_id, self.old) self.controller._set_graph_item_data(self.owner_id, self.item_kind, self.item_id, self.old)
class EditSimulationSettingsCommand(QUndoCommand):
def __init__(self, controller, owner_id: str, old: dict, new: dict) -> None:
super().__init__("Edit simulation settings")
self.controller, self.owner_id = controller, owner_id
self.old, self.new = old, new
def redo(self) -> None:
self.controller._set_simulation_settings(self.owner_id, self.new)
def undo(self) -> None:
self.controller._set_simulation_settings(self.owner_id, self.old)
class DeleteAnnotationsCommand(QUndoCommand): class DeleteAnnotationsCommand(QUndoCommand):
def __init__(self, controller, owner_id: str, annotations: dict[str, Annotation]) -> None: def __init__(self, controller, owner_id: str, annotations: dict[str, Annotation]) -> None:
super().__init__("Delete annotations") super().__init__("Delete annotations")

View File

@@ -13,6 +13,7 @@ from bedit.gui.controllers.commands import (
DeleteSelectionCommand, DeleteSelectionCommand,
DeleteAnnotationsCommand, DeleteAnnotationsCommand,
EditGraphItemCommand, EditGraphItemCommand,
EditSimulationSettingsCommand,
EditTextDefinitionCommand, EditTextDefinitionCommand,
EditComponentAppearanceCommand, EditComponentAppearanceCommand,
EditComponentPropertiesCommand, EditComponentPropertiesCommand,
@@ -37,8 +38,9 @@ from bedit.core.model import (
Port, Port,
clone_component, clone_component,
) )
from bedit.core.simulation import Simulation
from bedit.core.port_types import PortTypeRegistry from bedit.core.port_types import PortTypeRegistry
from bedit.core.serializer import JsonDocumentSerializer from bedit.core.serializer import DocumentSerializer
class DocumentController(QObject): class DocumentController(QObject):
@@ -60,8 +62,9 @@ class DocumentController(QObject):
filePathChanged = Signal(object) filePathChanged = Signal(object)
modifiedChanged = Signal(bool) modifiedChanged = Signal(bool)
def __init__(self, parent=None) -> None: def __init__(self, parent=None, *, simulation: Simulation | None = None) -> None:
super().__init__(parent) super().__init__(parent)
self.simulation = simulation or Simulation()
self.document: GraphDocument | None = None self.document: GraphDocument | None = None
self.active_component_id: str | None = None self.active_component_id: str | None = None
self.file_path: Path | None = None self.file_path: Path | None = None
@@ -105,7 +108,7 @@ class DocumentController(QObject):
self.filePathChanged.emit(None) self.filePathChanged.emit(None)
def load(self, path: Path) -> None: def load(self, path: Path) -> None:
self.document = JsonDocumentSerializer.load(path) self.document = DocumentSerializer.load(path)
self.active_component_id = next(iter(self.document.roots), None) self.active_component_id = next(iter(self.document.roots), None)
self.file_path = path self.file_path = path
self.undo_stack.clear() self.undo_stack.clear()
@@ -121,7 +124,7 @@ class DocumentController(QObject):
target = path or self.file_path target = path or self.file_path
if target is None: if target is None:
raise ValueError("No file path has been selected") raise ValueError("No file path has been selected")
JsonDocumentSerializer.save(self.document, target) DocumentSerializer.save(self.document, target)
self.file_path = target self.file_path = target
self.undo_stack.setClean() self.undo_stack.setClean()
self.filePathChanged.emit(target) self.filePathChanged.emit(target)
@@ -378,6 +381,23 @@ class DocumentController(QObject):
self.undo_stack.push(AddAnnotationCommand(self, self.active_component_id, annotation)) self.undo_stack.push(AddAnnotationCommand(self, self.active_component_id, annotation))
return annotation.id return annotation.id
def edit_simulation_settings(self, settings: dict) -> None:
component = self.active_component
if component is None or component.implementation_kind != "graph":
raise ValueError("Open a graph component before editing simulation settings")
old = deepcopy(component.graph.simulation_settings)
new = deepcopy(settings)
if old != new:
self.undo_stack.push(
EditSimulationSettingsCommand(self, component.id, old, new)
)
def compile_active_graph(self) -> None:
component = self.active_component
if component is None or component.implementation_kind != "graph":
raise ValueError("Open a graph component before compiling")
self.simulation.compile(component.to_dict())
def set_route_waypoints(self, item_kind: str, item_id: str, waypoints: list[QPointF]) -> None: def set_route_waypoints(self, item_kind: str, item_id: str, waypoints: list[QPointF]) -> None:
item = ( item = (
self.active_graph.connections self.active_graph.connections
@@ -1032,6 +1052,12 @@ class DocumentController(QObject):
if owner_id == self.active_component_id: if owner_id == self.active_component_id:
self.graphItemChanged.emit(item_kind, item_id) self.graphItemChanged.emit(item_kind, item_id)
def _set_simulation_settings(self, owner_id: str, settings: dict) -> None:
owner = self.document.find_component(owner_id) if self.document else None
if owner is not None and owner.implementation_kind == "graph":
owner.graph.simulation_settings = deepcopy(settings)
self.documentReset.emit()
def _replace_component(self, old_id: str, replacement: Component) -> None: def _replace_component(self, old_id: str, replacement: Component) -> None:
if self.document is None: if self.document is None:
return return

View File

@@ -3,7 +3,7 @@ from pathlib import Path
from PySide6.QtCore import QSettings, Signal from PySide6.QtCore import QSettings, Signal
from PySide6.QtWidgets import QDialog, QFileDialog from PySide6.QtWidgets import QDialog, QFileDialog
from bedit.core.libraries import default_library_paths from bedit.core.libraries import bundled_library_path, default_library_paths
from bedit.gui.preferences import application_settings from bedit.gui.preferences import application_settings
from bedit.gui.generated.ui_settings_dialog import Ui_SettingsDialog from bedit.gui.generated.ui_settings_dialog import Ui_SettingsDialog
@@ -56,8 +56,22 @@ class SettingsDialog(QDialog):
settings = settings if settings is not None else application_settings() settings = settings if settings is not None else application_settings()
value = settings.value("libraries/paths", default_library_paths()) value = settings.value("libraries/paths", default_library_paths())
if isinstance(value, str): if isinstance(value, str):
return [value] paths = [value]
return [str(path) for path in value] else:
paths = [str(path) for path in value]
bundled = bundled_library_path()
migrated = [
str(bundled)
if not Path(path).exists()
and Path(path).parent == bundled.parent
and Path(path).suffix.lower() == ".json"
else path
for path in paths
]
if migrated != paths:
settings.setValue("libraries/paths", migrated)
settings.sync()
return migrated
@staticmethod @staticmethod
def graph_grid_size(settings: QSettings | None = None) -> int: def graph_grid_size(settings: QSettings | None = None) -> int:
@@ -79,7 +93,7 @@ class SettingsDialog(QDialog):
self, self,
"Add library", "Add library",
"", "",
"BEdit libraries (*.json);;All files (*)", "BEdit libraries (*.beb *.bedit.json *.json);;All files (*)",
) )
if path: if path:
self._append_unique_path(path) self._append_unique_path(path)

View File

@@ -0,0 +1,22 @@
from copy import deepcopy
from PySide6.QtWidgets import QDialog
from bedit.gui.generated.ui_simulation_settings_dialog import Ui_SimulationSettingsDialog
class SimulationSettingsDialog(QDialog):
def __init__(self, settings: dict, parent=None) -> None:
super().__init__(parent)
self.ui = Ui_SimulationSettingsDialog()
self.ui.setupUi(self)
self._settings = deepcopy(settings)
self.ui.placeholderCheckBox.setChecked(
bool(self._settings.get("enabled", False))
)
@property
def settings(self) -> dict:
values = deepcopy(self._settings)
values["enabled"] = self.ui.placeholderCheckBox.isChecked()
return values

View File

@@ -215,6 +215,129 @@ ff\xa6\x5cP`\xb7\xadY\x1b\xde\x8b\xcb\x97/\xc3\
\x80\x90\x84\xfe\x83\xfe\x0fl\x00\x00/\xbf\xf2\xf2\x7f\x9c\ \x80\x90\x84\xfe\x83\xfe\x0fl\x00\x00/\xbf\xf2\xf2\x7f\x9c\
\xfd\x0bVu/\x8d\xbcS'\xfc\x00\x00\x00\x00IE\ \xfd\x0bVu/\x8d\xbcS'\xfc\x00\x00\x00\x00IE\
ND\xaeB`\x82\ ND\xaeB`\x82\
\x00\x00\x07\x88\
\x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\
\x09pHYs\x00\x00\x0d\xd7\x00\x00\x0d\xd7\x01B(\
\x9bx\x00\x00\x00\x07tIME\x07\xd9\x08\x0b\x00-\
\x1f\xb3\xb6|\x9d\x00\x00\x00\x06bKGD\x00\xff\x00\
\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x07\x08IDATx\
\xda\xbdWiLTW\x18\x95\xda\xa0P1]b\x89\
\x0a.EQ\xdcj\x5c\xd3FcMj\xda\xa4I-\
?J4\xc4(1\xadMl\xd5\x22*h,\x88K\
\xd0b+\xa6\x15Y\x0dV\x94\x08U\x16A\x10\x18`\
f`\x18\x96a\x1f\x86a\xd8\x94}\x11T\x90\xa0v\
N\xefy\x99\x99L\xa8\x88&\xd6\x97|\xb9\xf7\xdd\xf7\
\xbes\xce\xb7\xdc\xfbf&\xbc\xea\x15\x10\x10\xc0a\xa2\
\xb0\x0f\xd7\xaf_\xbfx\xcd\x9a5\x8b\xc5\xdcQ\xd8\xdb\
\x9e\x9e\x9e\x13\xde\xc4\xf5\x96L&\xd3<z\xf4\x08C\
CC\x92q\x9e\x9e\x9e\xae\xe6\xb37!`jbb\
b\xdf\xc8\xc8\x08\x86\x87\x87%\xe3\xfc\xc6\x8d\x1b\xbd|\
\xf6:\x89\xec\x97/_\xfe\xc9s\xd6?\xc8\xcc\xcc\x1c\
z\xf2\xe4\x09\x89%\xe3<##c\x88\xcfF\xbf\xbc\
z\xf5\xeaO\xc5`\xf7\xaa\xe4\xd3\x93\x92\x92\xaa\x9e>\
}\x0a__\xdf\x10\xa6\x965\xde\xbcy\xb3\xfb\xb5k\
\xd7\xd2\xef\xdf\xbf\x8f\xc1\xc1AK\x098\xef\xeb\xebC\
\x5c\x5c\x5c\xba\xbb\xbb\xfb7|W\x98MHH\xc8\x9f\
\xcf\x9e=CUU\x95\x8e\x98/K\xee\x94\x9b\x9b\xdb\
A\xd0\x81\x81\x01#\xeb\x1b\x15\x15\x95\xabP(\x1a\x19\
-\xd7\x1f>|\xf8\x5c\xe3\xb3\xc7\x8f\x1fC\xa9T6\
\xc4\xc7\xc7\xcb9'F\x7f\x7f?t:]\x0f\xb1\xc7\
\xad\xef\xcd\x9b7k\xe8\xd0\xdb\xdbKcd\x12\xb8X\
32ra\x9c\x8f6\xae[\x9bQ\x10K\xfe==\
=\xe8\xec\xecDww7D`\xcc\x84\xc3\x98\xecK\
\x96,\xf1lllDGG\x87\x91Nt\xb6\x16B\
#\x01\x05\xb5\xb7\xb7\xa3\xa6F+\x22\xd3\xa2\xad\xfd\x1e\
\x06\xfa-\x844\x89\xb0\xab\xabKz\xef\xee\xdd\xbbh\
ii1655\xc1\xcd\xcdm\xdb\x8b20\xcd\xcb\
\xcb\xab\x90\xce\xad\xad\xad\x14B \x8b\x08FZZZ\
\x0a\x95J\x85\xb2\xb22\x18\x0c\x06\xd4\xd7\xd7\xa3\xb4\xbc\
\x14\x05\xea<\xa85\x0atuw\x8a\x88\xbb\x18\xb5\x85\
\x9cAq\xdc\xb1c\x87\x8a\x1c\xe3\x95a\x9e\xbf\xbf\x7f\
3\x05\xd0\x89 \x8c\x88\xf3\xc2\xc2Bid$4\x02\
744\x083P\x8c4\x97\x17fA\xdf(\xb2\xd2\
\xd6\x86{\xf7\xeeI\xebuuu8r\xe4H3\xb1\
_\xa6\x09\xa7\x5c\xbdz\xb5\xddD\xc2\xd4ID\x15\x15\
\x15\xbc7Y\xb3467\xb7H\xf3\xc6\x86F)\x13\
z\xbd^\x90\xe9Q\xa4\xc9G\x9dAKA\xa2L5\
\x92oddd\xab\xc0~g\x5c\xf6\x0d\x1b6\xec2\
\xa5\x96\x00\x82\xa4\x99\x913\x0a\x0a1E\xde\x84\xd4\xcc\
X\xc4\xa7\xfe\x86\x84\xb4\xb3H\xbc\x1d\x05\xadVKr\
v\xbb4W\x15\xcbQ\xab\xd3J\xa5***Be\
e%\xc4\x99\xb0}<\xfe\x89'O\x9eT\x92\x8c&\
\x84P=\xf7\xb1\x14I\xbd\xbe\x1e\x9a\xb2b\x84_\xff\
\x19%\xf510t\xa7\xa0\xb1/\x0d\xd5-q\xf8+\
\xc5\x07\xd99)\xa8\xd3\xd5I\xefj4\x1a\xa8\xd4\x0a\
\xa8\x0b\xd5(..Fuu5\x04v\x1e9\xfes\
\xe2\x89\xed\x91#\x946\x09\xa5\x9d\x8c\x80/3\x0a\xa6\
T\xacS\xbdiM\x87#\xe77BYw\x1a%\xad\
\xe7\xa0\xeb\x8f\x82\xfe\xc1%Tt\xfc\x81\x82\x86_q\
\xe2\xd2zA\x5c\x86\xf2\xf2\x0a\xa9Q\x15\xca<\x8e\x14\
#\x05Q[[\x0br\x88\xfb&\xb9\x5c\x9eCn\x0a\
X\x91\x97':X\xadFII\x89p.\xb7DL\
1\xeczf\xa1\xba\xaa\x1a\x97\xaf\x85 8\xcd\x09\x19\
\xb5\xbb\x91\xd3\xbc\x07\x8a6\x1f(\xdb}\x90{w\x1f\
\xb2\xf4?!<\x7f\x11\xceE\xee%\x0e\xf7<\xb2\xb3\
\xb3I\xce\x00\x88Il\xe2Q\x04\xf2\xf3\xf3!\xb8?\
\xa6\x80\x8dB\x0d\xeb\xccT\xf1\x05\xb3\x08\x0a\xb0\x88\xaa\
\xa9\xd6\xc2\xffw\x0f\x9cW\xccDh\xd12$h\xbf\
FR\xbd\x07\x92\x0c\x1e\xf8[\xe7\x8e\xb0\xd2U8\xa7\
t\x82\xef\xb9\xb5P\x15\xa8q\xe7\xce\x1d\xe4\xe4\xe40\
\x10f\x8fAP\x0c9$.r\x0a\xee\xcf(`\x93\
\xf8\xbc\xf2\xf8d\xba\xa8\xce,\x82}`\xb9\xaf\x16\x02\
|\x83\xbeBH\x9e3N\xcb\xe7\xe0x\xfe\x1c\x04\xaa\
\x5c\x11X\xe0\x8a\x00\xe5\x5c\x9cR|\x84`\xf9l\xec\
\x0dZ,\xa2W0\x03\x8c\x92e\x1cM\xceu\x8a\xa3\
\x80\xcf)`mrr2\x15Q\x849\x13t\xa0z\
\xf6\x00\xef\x05H%\x02\x82\xbeGP\xea\x0c\x9c\x92\xcd\
\xc5Q\xd9<\x1c\x92-\xc0A\xd9B\xf8\xc9\x84\x88l\
\x17\x04\xcbf\xe1\x07\xdfu(((\x14V@_\xf6\
\x12Gf\xd2BN\xae\x94\x94\x14\x0aXC\x01\xb3\x8e\
\x1d;\xd6\x1e\x1d\x1d=\x12\x1b\x1b\xfb\x0f\xb3`]\x0e\
\x8a\xe0=\xe7\xf1\x09\xd7\xe1\xf5\xcb\xbb\x08\xca\x9a\x83\xc3\
\xe9\xf3\xb0/m!~Lu\xc3\xfe\xb4\x05\xf0\xcf\xf8\
\x08\xde\xe1\xd3p2\xe8(\xfb\x89Fr\xfaY\x22'\
69\xc8%8\xdb\xc8=\xc1\xd6\xd6\xd6\x86\x13a+\
\x99\x92C\x87\x0e\x19\xa8\xd2\x5c\x0e\xa6P|\x09%\x00\
\xb5\xb8\xf7>\xe8\x85\xefN\xbc\x8fc\xb7f\xc3\xe7\x96\
+v'\xbb\xe1p\xaa\x10\x13:\x0d\xdbwmb\xea\
\x05y\x11\xb3\xc9\xb3\xc4B\xce\x8c\x08\xeczr\x90\x8b\
\x9c\x93'O&\xb7\xd5\xc5'+W\x1e\xa7\x83UO\
0\x0b\xe2>_\x02J\xcfH\xc3A\xbf=\xf8v\xe7\
Rx\xfa\xcc\xc06\xdf\xe9\xf0\xd8%2\xb1\xcf\x0bI\
\xc9\x89\xa2Wj\xe8\xc3\xc6\xb3\xae\xb9\xb4\xb6b\xc5\x8a\
\xe3/s\x14\xbb^\xbcx\xf1\x81Pl\xa43\x1d\xcb\
+\xcb\xa0P\xcb\x90\x9d\x95-uwrR\x12\x22\xa2\
\xc2\x10|6H\xd8i\xc4\x5c\x8eFn\x9e\x8c\xa4\xdc\
r\xdc=l`\xd6\x9d\xa5` \xc6\xd0\xd0\xd0\x07\xc4\
~\x19\x01\xb6\xde\xde\xde\x1a6\x0a;\x95\xc6]\x92)\
O\xc6-\xc5U\xdc\xbe\x9d\xce4\xf3\x84\x93\x1aU\xda\
\xb2\x15U\xect6\x9b\xf9\xf8f\xfa\xf9\xcc\xd2\x0f\x07\
\x0e\x1c\xd0\x10{<r\x9b\xfd\xfb\xf7\x9fa\xbdx\x88\
\x90\x98\x87\x14S\xc8\xba\x96jJ\xa07h\x19!\xc1\
\xcd\x8d\xca3\x83\xc76?\x5c\xec\x19\x9a\xf9\x9b`\xf9\
|\xf3]??\xbf3\xe4\x18\x93\xdd\xc5\xc5\xe5K:\
1\xfdt2\x9d\x8e\xe6H-\xdf\x05\xa6\xd9\xfa\xe3D\
B\x1e\xb3\xd6f}\x00\xb1\x8c,\x03\xb1\xe7\xcf\x9f\xff\
\xc5\x8b2\xb0L\xd4\xaa\x8f\xc4\xa6\xfa3\x02\xb3\x00#\
\xc1L\xc7*\x8d\x04c\x99\x91#E\xd3\x97\x18\xc4\x14\
\xbd\xd5K\x8e1\xd9\x1d\x1c\x1c8\xb8'$$\x0c\x0b\
'Fod4b\xcf\xf6\x84\x87\x87\x0f\xb2\xb6\xcf#\
\xb7^\xe3;\x11\x11\x11\x83\x81\x81\x81=\xa6o\x89\x91\
X\xe2G\xea0\xb1\xa7N\x1d\xe7\xaf\x83\xbd\xbd=\x7f\
\x82{\x84\x85\x85\xddgCm\xd9\xb2\x85?\xa3\x96\x0a\
[\xe5\xec\xec|A\xfcLo1\xa5w\xb4\xb1\xc6\xcd\
NNN\x17\xf8.}\xb6n\xdd\xaa\x22\x06\xb1\x889\
e\xca\x14b\x8f\x7f\xd9\xd9\xd9qX\xe7\xe8\xe8xB\
\x8c3\xad\x9bT\xd8\xa2+W\xae<\xb2\x16\xc19\xd7\
\xf8lT\x93\xcd$\x06\xb1\x88\xf9\xba\xae\xf7bbb\
\xbaX\x1a6\x15\x8ds\xae\xf1\xd9\x847p\xd9\x07\x07\
\x07k\xb9\x03\xf8\xb3\x9b\xc69\xd7\xf8\xec\x7fg\x9f4\
i\x92\x8d\xe9,\xdf)\xcc\x87\xc69\xd7\xf8]yU\
\xbc\x7f\x01\x06A\xf8\xf9\xb2\x87\xf2i\x00\x00\x00\x00I\
END\xaeB`\x82\
\x00\x00\x05\x08\ \x00\x00\x05\x08\
\x89\ \x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
@@ -1360,6 +1483,144 @@ $&\x93\xc8\x7f\xb3\xe7\xcc*\xc2w\xf1nHG\x09\
\xf9\x5c\x8a\xda\xed\x0c\xf6t\xf6\xe1\xf9-'\x97\x16\xdc\ \xf9\x5c\x8a\xda\xed\x0c\xf6t\xf6\xe1\xf9-'\x97\x16\xdc\
5\x03\xef$\x18\xdea\xfc\x07;\x00\xdf\x09\xb1\xdb|\ 5\x03\xef$\x18\xdea\xfc\x07;\x00\xdf\x09\xb1\xdb|\
\x84\x00\x00\x00\x00IEND\xaeB`\x82\ \x84\x00\x00\x00\x00IEND\xaeB`\x82\
\x00\x00\x08{\
\x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\
\x00\x00\x00\x09pHYs\x00\x00\x03v\x00\x00\x03v\
\x01}\xd5\x82\xcc\x00\x00\x00\x19tEXtSof\
tware\x00www.inksca\
pe.org\x9b\xee<\x1a\x00\x00\x07\xf8ID\
ATx\xda\xb5W\x0bX\xcdi\x1a\xff,\xd5\xa2\xc1\
\x8e\x8ad<\xe52.\xab\xc9\x0e\xe5t*a\x0c!\
\x95\xa2\x9abZ\x97U\xf3<\x96M3I5\x83\xd5\
E24\x88RjY\xc3&\x0c]\xb6D\xd3\x95t\
t\x8fD[\x9d\xee\xb7\xa3\x9e\xd2)\xba\xfd\xf6\xfdR\
\xf3\xa4Q\x1d\xb3\xcf~\xcf\xf3{\xce9\xdf\xff\xfd\xfe\
\xef\xef{\xef\x87\x01\xf8\xbf\x80\xd6(\x82\x02\xe1\x03\xc2\
x\xc2\x18\xbe7X\xae_x\xf4)\x7f\xff\xd5\xf49\
\x8e\x0b\xfd\x8f\x8a\x7f\xa7\xac\xac\xac\xe8\xe5\xe5e\x1b\x15\
\x1d\xf5$;;\xbb;//\xefUVvV\xd1\xd5\
\xabWWr]o\xcb\xd3\xc6\x95+W\xdc_\xbe|\
\x89R\xb1\xf8UXx\xf8~\xbe\xf7[o=g\xce\
\x1c\xe5\x90\x90\x90\xc2\xfc\xc7\xf9(+/Cuu5\
jkk!\x91HPRZ\xd2q\xe8\xd0\xa1\xf5\x03\
/\xc9\xd7\xa4\xe2\xe2\xe2\xd7\xaf;:P]S\x83\xb4\
\x87\x0f{\xec\xec\xec\xcc\xf8M~\x03\x81\xdf\xef\xdd\xbb\
\xf7\x87\xb4\x87i\xa8\xac\xaaD\x83\xa4\x01\x12Btt\
\xf4kWW\xd7\xeaE\x8b\x16]TTT\x9c\xcf\xdd\
\xf1\x16\x813g\xce\xdc\xae\xac\xac\x84\xb8\xbc\x1c\xa2G\
\x8f`\xef\xe0\xd0\xbax\xf1\xe2\x19\xef\xeb\x0eZ\x93\x8f\
\x1f\xf7-\xab\xa8(\xefU\xdc\xda\xda\xca-\xd0\xad\xa4\
\xa4\xf4\x1d=[F\x98\xceI\x0e\xb6\xc0\x18\xc2\xbc\x8b\
\x97.\x15\xdf\x89\x8b\xebIJNF[[\x1b6[\
Z\xa5\xd3\xfe\x04YIp9\x15\x15\x95YQQ\x91\
\xdd\x9c@SS\x13\xda\xdb\xdbPZZ\xfa\x8a\xbf\x9f\
\xa00\x5c\x10\xf2\x87\x0bW\xafYs\xd6v\xeb\x97\xad\
\x05\x05\x05\x90\x12\x89\x8d\xe6\x16Q\xea\xea\xea\x93d!\
AK\xce\xd2\xd2\xd2.%%\x19999\x90\xbc\x90\
\xf4\x92\xa8\xab\xaf\x83\xa1\xa1\xe1\x12\xee\xd2\x01\xd91\xa6\
?+\xdez\x01a\x9a\xbc\xbc\xbc\xf9\xe7\xab\xd7\x14<\
\x7f\xfe\xbc\xd7\x84\xebML\x13\x96-[\xa6<\x12\x09\
Z\x8a\xbbw\xef\xfe\xd7\xdd{q\xc8\xc8\xc8@Uu\
\x15\xaa\x09\x15\x15\x15\xb8r\xf5\xc7V\x1b\x1b\x9b5\x5c\
\xe6\xb0\xc7\xe1\x8d\xc1\xc1A\x05_\xd1\xe2:\x7f\x95B\
\x84\x89\xdc_\x02]afIi)\x9a\x9b\x9bac\
\xfbe\xba\x85\x85\xc5\xd4\xe1\x02\x93\xd6\x94s\x01\xe7Z\
\x92\x92\x93\xb8\xd9\xf1 \xed\x01\x0a\x0b\x0b\xf1\xecY!\
\x92S\x92p\xd2\xef\xfb\xce\xd8;\xb1\x92\xd4\xd4T\xf8\
\xfa\x1ek\xd7\xd1\xd1\xb1\xe1\xf10T\x01\x19O\x10\x0a\
\x04\xba\xa9t\x032c=\x1c\xbe\xda\x9d\xbfk\xd7\xae\
\x19\xef\x22\xc1\xd3\xd6\xca\xca\xca\xfa\xe6O7\x10p>\
\x08!\xff\x08\x81\xe7QO\xf8\xf8\xfa\x22+;\x93\x02\
;\x1d!\xa1!\xe0\xca\xc3\xc2\xc2(\xc8\xed\xcb\xe9\xcc\
,\xfe\xae\xe1\xaa\xd88\x82\xb6\xbe\xbe\xc1\xdd\xba\xbaz\
TP\x968\xefw)rvv\x9e3\x98\x04\x0f\xd6\
\xc3\x87\x0f%\xe4\xe5\xe5b\x87\xbd=\x5c\x5c\xbf\x81(\
\xe3!\xf4\xf4\x84\xa4\x5c\x84;q\xb1\x08%R\x91\x91\
\x11\xf0\xf4\xf4\xe8\xa6Z\xf15?\xc3\xcf\x8eTJ\xc7\
\x12\xfedh\xb8<\xb2\xb1\xb1\xb1\xa7\xb8\xa4\x04\x01\x81\
\x81\xe5TL\x16\x0e\x08*y\xf2\xfd\x0e\xba]\x17\xcf\
\x9e\xa0\xe0\x0b\xd8ha\x86\xfc\xc7\xb9ptr\xc4\x8d\
\x9b7@\xae\x81\xb7\xb7\x17\xdc\xbfu\xeb\xd6\xd4\xd4\xfc\
\x91\xceh\xf4\x17;Y\xeb\xb9\xe6\x8a\x15+\xc3ZZ\
Z\xba\x9f\x17\x15!]$\xaa;q\xe2\xc4\x12n%\
r\x8b3/\xb7\xe1\xe1\xd7\x90\x90\x90\x80\xb3g\xcf\xc2\
\xda\xfa\x0bxy{\x83d\xe0\xe6\xee\xd6\xe3\xe8\xf8\xb7\
\xd7\xdb\xb6o\xab\xa7\x9b\x1f\xa33\xdc\x82r\xfd:d\
-0\xf2\x84\x05\xe6\xe6\xe6\x17\xa5Ri\xd7\xe3'O\
@\xf5\xbd\xe9\xe0\xc1\x83\x17sss{\xbc\xbc<\xb1\
b\xe5r\x1c8\xe0\x82\xd3\xa7O\xc3\xc5\xc5\xa5\xd5\xd6\
\xd6\xf6\xa9\x96\x96\xd69:gM\xd0%\xcc$|\xd8\
_\x05e%08M\xe7n\xd9\xb2%\x80\xfaFg\
Nn.'\x01\x0fO\x0f,_n\x88\xcd\x9b7\xe1\
\xf2\xe5\xcb \xc5i$\xb7\x81\xf01aJ_7\x94\
\x1f*\x8d\x07m\xc8d\x89\x85\xdb\xb7o\x7f\xc2\x8bL\
Ff\x16\xe5|&v\xd9\xef\x02u:888\xe4\
\xd3s\x01W*k/y\xdf\xfe\xae\xe8\xed\xed}\xf3\
\xde\xbd{\xe4g+P\xff\xa0xx\xd4K\xc2\xc7\xc7\
\xa7!1\xd4\xf2N\x84\xbb\xfaw\x03\xeb\xfdH\x90Y\
\xf9$Z\xd4\xb4\xe2\xf2\xf3\xf3\xb1u\xab-\x96\x0a\xb4\
\xa1o\xa0\xcf\x8b\x0e\xa5Z\x06\xca\xf2\xfcz\xa4e!\
(\xba\xa0\x88\xa0\x1dl_\x7f\xa0\x8d\x04\x99\x06\x0c\x0a\
&\x15\xea\xf1\xf7333a\xfd\x85\x15\x0c\x0c\xf5\xa1\
\xa5\xf5\x09\x8e\x1d;\x06rG\x9b\xf8\xd1\xdf;:\xcb\
>D\xdb\xd3y\x90\xde\x1c\x0f\x91\x1fk\xf8h2S\
\x93\xc5\x0a#*\x17\x08\x04j\xd7\xae]\xcbNII\
\xc1\xa6M\xe6tk=\xcc\x9f?\xbf7\xc5N\x9e<\
\xd9\xeaj16\x5c|\x95u7\xddg\xe8\x8egx\
\x95\xccP\x13\xcb\xf0\xef#,\xf2\xaf\x9f\xb1\x05\xdc\x12\
\xc3\x11\x19V\xf9\xaaU\xabf\xd20\xf1466\x96\
:\xa3\x19\xf4\xf4u1k\xf6LP\xfa!((\xa8\
\x9d\x88\xec\xfb\x5c\x8b9\xdc\xfe\x9a\xb5\xd7]fh\x89\
c\xe8,a\x90\x1604g04\x8a\x14\xba\x13}\
\x14\xc3\x94\x95\x99\xe2P$\x86Tnjj\xba >\
>\xbe4<<\x1c\xa6f&\x10\xea\x090c\xc6G\
\xd8\xb3g\x0fO\xb7\x0e===\x1elj\x04\xd5y\
j\xcc\xde\xc7\x96\x15\xfd\xec3\xaaK\x92B$\xc4\x0c\
]\xe5\xa3\xd1.6AQ(C\xe4>\x16MM\xfd\
\x9dm\x9d\xbd\xab\xb1P_\xff\x94JkuH\xc8\x05\
l05\x86@\xb8\x14jj\xd3(\xf8\xb6\xe2\xfa\xf5\
\xeb]FFF\xa7Hn\x06\x97\xed\xeb\xa0\xfc\xe5\x9a\
\x1fOc.\x0fN\xb2\xce\x17\x0f\x88D\xc5\x04t\xd4\
\xedEC\xb2\x12\x8a\x7f\xea%\xf1d\xc9\x5c\xa64\x98\
\xc4\xaf\x94S\xa11\x10\x89D\x12\xbf\x1f\xfc`l\xb2\
\x9e\xa2]\x07SU\xa7\xc2\xd8\xd8\x18QQQ=D\
\xee2\xefd\x83+\x1a\xffM\x98\xe9m\xcd\xd2\x8b\xc9\
\x1d\x0d\x89\x14\x0f\xc5c\xd1Qk\x0bi\x8e\x12\xc41\
\x14\x17\xfbX\xa9\xd9\xa7Lu`\x8dxK9\xd5\xf5\
\xd5T\xd7\x9b=<<\xb0~\xc3:\xe8,]\xd2\xab\
\xdc\xc0\xc0\x00111=;w\xee\x8cf\xecM`\
\x0d5\x94N\x99\xc8V\x86\xda\xb3\x17\xff\xf9'C9\
\xdd\x5c\x92\x22\x87\x8ej#H\xb3'\xa3\xe2.C\xcc\
7\xac\xc6Z\xc0\xd4\xfbI\xfc\xa2\x9c\xa6Y3\x1a\xc5\
Z\x1f\xd1Pz\xfc\xfb\xe3\x10\x0au1e\x8a\x0ah\
\x92\x05\x0fB''\xa7\xfb\xbc3\x12\x14F(V\x13\
\xc7)0\x93\xf3;Y}m\xfc\x1b\x12\xf5?\x8fF\
G\x956\xa4Y\x7f@u\x02\xc3\xdd\x03\xec\x85\xfb\x06\
\xf6GN\xa2\xf7\x90\x9a\x9a\xdat\xba\xb9\xd4\xd3\xd3\xf3\
\xd9\xbau\xeb\x22)\xbd\x1a]\x5c\xf6\x83\xba\x17\x22\x22\
\x22@\xed\x97\x97X\xa1\x0c\x7f\x5c\xfaILR\x18\xc3\
V\xfb\xff\x99U\xd5\x10\x89\xca\xdb\x94\x9a1\xa3\xd0Y\
9\x1b\xd2LE\xd4\xa52dy\xb3\x12^Y\x09L\
\xde\xcd\xcd\xcd\x8e\x22\xbe\x89\xbe/'\xcc^\xbbv\xad\
3\x99\x9c\xfb\x9c\xe7{)\xed}F\xf8\xe0}&\xe4\
\xbe\x89z\x85\xdf\x16VR\x15K$\x22\xde\xa0\xb3\x5c\
\x09\xcd)\x0a\x90\x5cb\xed}\xcd\x8a\x8d\xa5?\x0d\xdf\
\xc6\xc5\xc5\xb5ihh\xcc\xe5&vtt\xdc\x96\x98\
\x98\xd8\x13\x18\x18X\xab\xa0\xa0`\xc2\xd8\xe0\x14\x92\xbd\
w\x10\xf4\x8fnf\x85\xe2(\x86\xaah\x06\xa9\x88\xa1\
=i4\xe2\x8f\xb2\x87\xf4L\xa9\xd7\x02rrr\xda\
4\xab\xb5\xdf\xbau\xab\x90>\x03(\xd5jN\x9d:\
\xd58a\xc2\x04[.$cg\x1bn\xbe\x14\x1c1\
g9\xf9~\x0c\xe2\xf3\xac'\xdd\x8fU\xd0\xdeb\xae\
\xbb_HE(\x14\xfa\x04\x07\x07\xb7\xf8\xfb\xfb\xb7\x91\
\xcf\xc5\xaa\xaa\xaa\x7f\xe1&\x92Q\xb9,\xa3\x9d\xa6\xb6\
\x06s\xb2\xd6cG\xe8\xfb'}{\xa3~\xc9\x02\xc2\
t\x82\x11a\x13a)a\xb2L\xcae'!\xc7]\
\xc91\xb0?\xfc\x17\xb0l\xc1\xc4\x9c\xd3\xd1\xfc\x00\x00\
\x00\x00IEND\xaeB`\x82\
\x00\x00\x04+\ \x00\x00\x04+\
\x89\ \x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
@@ -2271,6 +2532,10 @@ qt_resource_name = b"\
\x00z\ \x00z\
\x00o\x00o\x00m\x00-\x00o\x00r\x00i\x00g\x00i\x00n\x00a\x00l\x00.\x00p\x00n\x00g\ \x00o\x00o\x00m\x00-\x00o\x00r\x00i\x00g\x00i\x00n\x00a\x00l\x00.\x00p\x00n\x00g\
\ \
\x00\x0d\
\x0f\xa5%\xc7\
\x00r\
\x00u\x00n\x00-\x00b\x00u\x00i\x00l\x00d\x00.\x00p\x00n\x00g\
\x00\x0f\ \x00\x0f\
\x0bb\xb8g\ \x0bb\xb8g\
\x00d\ \x00d\
@@ -2327,6 +2592,11 @@ qt_resource_name = b"\
\x00e\ \x00e\
\x00d\x00i\x00t\x00-\x00r\x00e\x00d\x00o\x00.\x00p\x00n\x00g\ \x00d\x00i\x00t\x00-\x00r\x00e\x00d\x00o\x00.\x00p\x00n\x00g\
\x00\x16\ \x00\x16\
\x01p\xe1\x87\
\x00p\
\x00r\x00e\x00f\x00e\x00r\x00e\x00n\x00c\x00e\x00s\x00-\x00s\x00y\x00s\x00t\x00e\
\x00m\x00.\x00p\x00n\x00g\
\x00\x16\
\x0c\xb7\x89\xc7\ \x0c\xb7\x89\xc7\
\x00d\ \x00d\
\x00r\x00a\x00w\x00-\x00b\x00e\x00z\x00i\x00e\x00r\x00-\x00c\x00u\x00r\x00v\x00e\ \x00r\x00a\x00w\x00-\x00b\x00e\x00z\x00i\x00e\x00r\x00-\x00c\x00u\x00r\x00v\x00e\
@@ -2383,59 +2653,63 @@ qt_resource_struct = b"\
\x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
\x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x1a\x00\x00\x00\x03\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x1c\x00\x00\x00\x03\
\x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x01\xde\x00\x00\x00\x00\x00\x01\x00\x00G\x5c\ \x00\x00\x01\xfe\x00\x00\x00\x00\x00\x01\x00\x00N\xe8\
\x00\x00\x01\x9f{C\xf1'\ \x00\x00\x01\x9f{C\xf1'\
\x00\x00\x02\x9a\x00\x00\x00\x00\x00\x01\x00\x00dK\ \x00\x00\x02>\x00\x00\x00\x00\x00\x01\x00\x00Z\x1a\
\x00\x00\x01\x9f\x7f\xa8\xa8)\
\x00\x00\x02\xec\x00\x00\x00\x00\x00\x01\x00\x00tV\
\x00\x00\x01\x9f{0\xc99\ \x00\x00\x01\x9f{0\xc99\
\x00\x00\x01\xa4\x00\x00\x00\x00\x00\x01\x00\x00<\xa6\ \x00\x00\x01\xc4\x00\x00\x00\x00\x00\x01\x00\x00D2\
\x00\x00\x01\x9f\x7f&\x83\xcd\ \x00\x00\x01\x9f\x7f&\x83\xcd\
\x00\x00\x01\x84\x00\x00\x00\x00\x00\x01\x00\x004\xbe\ \x00\x00\x01\xa4\x00\x00\x00\x00\x00\x01\x00\x00<J\
\x00\x00\x01\x9f{C\xf1\x18\ \x00\x00\x01\x9f{C\xf1\x18\
\x00\x00\x03R\x00\x00\x00\x00\x00\x01\x00\x00}\x9f\ \x00\x00\x03\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x8d\xaa\
\x00\x00\x01\x9f\x7fY\xceg\ \x00\x00\x01\x9f\x7fY\xceg\
\x00\x00\x02P\x00\x00\x00\x00\x00\x01\x00\x00V\xbd\ \x00\x00\x02\xa2\x00\x00\x00\x00\x00\x01\x00\x00f\xc8\
\x00\x00\x01\x9f\x7fV\xd5\xc0\ \x00\x00\x01\x9f\x7fV\xd5\xc0\
\x00\x00\x03\x08\x00\x00\x00\x00\x00\x01\x00\x00t\xba\ \x00\x00\x03Z\x00\x00\x00\x00\x00\x01\x00\x00\x84\xc5\
\x00\x00\x01\x9f\x7f&\x83r\ \x00\x00\x01\x9f\x7f&\x83r\
\x00\x00\x02|\x00\x00\x00\x00\x00\x01\x00\x00]P\ \x00\x00\x02\xce\x00\x00\x00\x00\x00\x01\x00\x00m[\
\x00\x00\x01\x9f\x7f&\x83\xb1\ \x00\x00\x01\x9f\x7f&\x83\xb1\
\x00\x00\x00\xfc\x00\x00\x00\x00\x00\x01\x00\x00$|\ \x00\x00\x01\x1c\x00\x00\x00\x00\x00\x01\x00\x00,\x08\
\x00\x00\x01\x9f\x7fY\xce\x82\ \x00\x00\x01\x9f\x7fY\xce\x82\
\x00\x00\x01\xc0\x00\x00\x00\x00\x00\x01\x00\x00C\xdc\ \x00\x00\x01\xe0\x00\x00\x00\x00\x00\x01\x00\x00Kh\
\x00\x00\x01\x9f{C\xf1.\ \x00\x00\x01\x9f{C\xf1.\
\x00\x00\x01d\x00\x00\x00\x00\x00\x01\x00\x00/\x10\ \x00\x00\x01\x84\x00\x00\x00\x00\x00\x01\x00\x006\x9c\
\x00\x00\x01\x9f{{\xa5\xd5\ \x00\x00\x01\x9f{{\xa5\xd5\
\x00\x00\x03x\x00\x00\x00\x00\x00\x01\x00\x00\x81\xdf\ \x00\x00\x03\xca\x00\x00\x00\x00\x00\x01\x00\x00\x91\xea\
\x00\x00\x01\x9f\x7f&\x83\x10\ \x00\x00\x01\x9f\x7f&\x83\x10\
\x00\x00\x00\xde\x00\x00\x00\x00\x00\x01\x00\x00 \xd9\ \x00\x00\x00\xfe\x00\x00\x00\x00\x00\x01\x00\x00(e\
\x00\x00\x01\x9f\x7f&\x83~\ \x00\x00\x01\x9f\x7f&\x83~\
\x00\x00\x02\xe4\x00\x00\x00\x00\x00\x01\x00\x00p\xd7\ \x00\x00\x036\x00\x00\x00\x00\x00\x01\x00\x00\x80\xe2\
\x00\x00\x01\x9f\x7f&\x83T\ \x00\x00\x01\x9f\x7f&\x83T\
\x00\x00\x00^\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xc1\ \x00\x00\x00~\x00\x00\x00\x00\x00\x01\x00\x00\x14M\
\x00\x00\x01\x9f\x7fY\xce^\ \x00\x00\x01\x9f\x7fY\xce^\
\x00\x00\x00\x82\x00\x00\x00\x00\x00\x01\x00\x00\x11\xcd\ \x00\x00\x00\xa2\x00\x00\x00\x00\x00\x01\x00\x00\x19Y\
\x00\x00\x01\x9f{0\xc9B\ \x00\x00\x01\x9f{0\xc9B\
\x00\x00\x01D\x00\x00\x00\x00\x00\x01\x00\x00,\xb6\ \x00\x00\x01d\x00\x00\x00\x00\x00\x01\x00\x004B\
\x00\x00\x01\x9f\x7f&\x83)\ \x00\x00\x01\x9f\x7f&\x83)\
\x00\x00\x02\xc2\x00\x00\x00\x00\x00\x01\x00\x00kU\ \x00\x00\x03\x14\x00\x00\x00\x00\x00\x01\x00\x00{`\
\x00\x00\x01\x9f{C\xf1N\ \x00\x00\x01\x9f{C\xf1N\
\x00\x00\x02\x1e\x00\x00\x00\x00\x00\x01\x00\x00R\x8e\ \x00\x00\x02p\x00\x00\x00\x00\x00\x01\x00\x00b\x99\
\x00\x00\x01\x9f\x7f&\x82\xf2\ \x00\x00\x01\x9f\x7f&\x82\xf2\
\x00\x00\x00\xb0\x00\x00\x00\x00\x00\x01\x00\x00\x1a9\ \x00\x00\x00\xd0\x00\x00\x00\x00\x00\x01\x00\x00!\xc5\
\x00\x00\x01\x9f{\x8d\xf34\ \x00\x00\x01\x9f{\x8d\xf34\
\x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ \x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
\x00\x00\x01\x9f{0\xc9+\ \x00\x00\x01\x9f{0\xc9+\
\x00\x00\x01\xfe\x00\x00\x00\x00\x00\x01\x00\x00J\xbc\ \x00\x00\x02\x1e\x00\x00\x00\x00\x00\x01\x00\x00RH\
\x00\x00\x01\x9f{C\xf1=\ \x00\x00\x01\x9f{C\xf1=\
\x00\x00\x006\x00\x00\x00\x00\x00\x01\x00\x00\x05\x86\ \x00\x00\x006\x00\x00\x00\x00\x00\x01\x00\x00\x05\x86\
\x00\x00\x01\x9f\x7f&\x83\x99\ \x00\x00\x01\x9f\x7f&\x83\x99\
\x00\x00\x03\xa2\x00\x00\x00\x00\x00\x01\x00\x00\x84u\ \x00\x00\x03\xf4\x00\x00\x00\x00\x00\x01\x00\x00\x94\x80\
\x00\x00\x01\x9f{{\xa5\xe3\ \x00\x00\x01\x9f{{\xa5\xe3\
\x00\x00\x01$\x00\x00\x00\x00\x00\x01\x00\x00'\xef\ \x00\x00\x00^\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xc1\
\x00\x00\x01\x9f\x7f\xac\xf2\xc6\
\x00\x00\x01D\x00\x00\x00\x00\x00\x01\x00\x00/{\
\x00\x00\x01\x9f\x7fV\xd5\xe5\ \x00\x00\x01\x9f\x7fV\xd5\xe5\
\x00\x00\x03*\x00\x00\x00\x00\x00\x01\x00\x00x\xac\ \x00\x00\x03|\x00\x00\x00\x00\x00\x01\x00\x00\x88\xb7\
\x00\x00\x01\x9f{0\xc9R\ \x00\x00\x01\x9f{0\xc9R\
" "

View File

@@ -18,9 +18,9 @@ from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
QTransform) QTransform)
from PySide6.QtWidgets import (QApplication, QDockWidget, QFrame, QHBoxLayout, from PySide6.QtWidgets import (QApplication, QDockWidget, QFrame, QHBoxLayout,
QHeaderView, QLabel, QMainWindow, QMenu, QHeaderView, QLabel, QMainWindow, QMenu,
QMenuBar, QSizePolicy, QSpacerItem, QSplitter, QMenuBar, QPlainTextEdit, QSizePolicy, QSpacerItem,
QStackedWidget, QToolBar, QToolButton, QTreeView, QSplitter, QStackedWidget, QTabWidget, QToolBar,
QVBoxLayout, QWidget) QToolButton, QTreeView, QVBoxLayout, QWidget)
from bedit.gui.editors.text_definition import TextDefinitionEditor from bedit.gui.editors.text_definition import TextDefinitionEditor
from bedit.gui.graphics.workspace import GraphWorkspaceView from bedit.gui.graphics.workspace import GraphWorkspaceView
@@ -31,77 +31,87 @@ class Ui_MainWindow(object):
if not MainWindow.objectName(): if not MainWindow.objectName():
MainWindow.setObjectName(u"MainWindow") MainWindow.setObjectName(u"MainWindow")
MainWindow.resize(1209, 777) MainWindow.resize(1209, 777)
self.actionSimulationSettings = QAction(MainWindow)
self.actionSimulationSettings.setObjectName(u"actionSimulationSettings")
icon = QIcon()
icon.addFile(u":/icons/icons/preferences-system.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionSimulationSettings.setIcon(icon)
self.actionCompile = QAction(MainWindow)
self.actionCompile.setObjectName(u"actionCompile")
icon1 = QIcon()
icon1.addFile(u":/icons/icons/run-build.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionCompile.setIcon(icon1)
self.actionNew = QAction(MainWindow) self.actionNew = QAction(MainWindow)
self.actionNew.setObjectName(u"actionNew") self.actionNew.setObjectName(u"actionNew")
icon = QIcon() icon2 = QIcon()
icon.addFile(u":/icons/icons/document-new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon2.addFile(u":/icons/icons/document-new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionNew.setIcon(icon) self.actionNew.setIcon(icon2)
self.actionRotateClockwise = QAction(MainWindow) self.actionRotateClockwise = QAction(MainWindow)
self.actionRotateClockwise.setObjectName(u"actionRotateClockwise") self.actionRotateClockwise.setObjectName(u"actionRotateClockwise")
icon1 = QIcon() icon3 = QIcon()
icon1.addFile(u":/icons/icons/transform-rotate.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon3.addFile(u":/icons/icons/transform-rotate.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionRotateClockwise.setIcon(icon1) self.actionRotateClockwise.setIcon(icon3)
self.actionZoomIn = QAction(MainWindow) self.actionZoomIn = QAction(MainWindow)
self.actionZoomIn.setObjectName(u"actionZoomIn") self.actionZoomIn.setObjectName(u"actionZoomIn")
icon2 = QIcon() icon4 = QIcon()
icon2.addFile(u":/icons/icons/zoom-in.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon4.addFile(u":/icons/icons/zoom-in.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionZoomIn.setIcon(icon2) self.actionZoomIn.setIcon(icon4)
self.actionZoomOut = QAction(MainWindow) self.actionZoomOut = QAction(MainWindow)
self.actionZoomOut.setObjectName(u"actionZoomOut") self.actionZoomOut.setObjectName(u"actionZoomOut")
icon3 = QIcon() icon5 = QIcon()
icon3.addFile(u":/icons/icons/zoom-out.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon5.addFile(u":/icons/icons/zoom-out.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionZoomOut.setIcon(icon3) self.actionZoomOut.setIcon(icon5)
self.actionCenterView = QAction(MainWindow) self.actionCenterView = QAction(MainWindow)
self.actionCenterView.setObjectName(u"actionCenterView") self.actionCenterView.setObjectName(u"actionCenterView")
icon4 = QIcon() icon6 = QIcon()
icon4.addFile(u":/icons/icons/zoom-original.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon6.addFile(u":/icons/icons/zoom-original.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionCenterView.setIcon(icon4) self.actionCenterView.setIcon(icon6)
self.actionOpen = QAction(MainWindow) self.actionOpen = QAction(MainWindow)
self.actionOpen.setObjectName(u"actionOpen") self.actionOpen.setObjectName(u"actionOpen")
icon5 = QIcon() icon7 = QIcon()
icon5.addFile(u":/icons/icons/document-open.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon7.addFile(u":/icons/icons/document-open.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionOpen.setIcon(icon5) self.actionOpen.setIcon(icon7)
self.actionReloadLibraries = QAction(MainWindow) self.actionReloadLibraries = QAction(MainWindow)
self.actionReloadLibraries.setObjectName(u"actionReloadLibraries") self.actionReloadLibraries.setObjectName(u"actionReloadLibraries")
self.actionSave = QAction(MainWindow) self.actionSave = QAction(MainWindow)
self.actionSave.setObjectName(u"actionSave") self.actionSave.setObjectName(u"actionSave")
icon6 = QIcon() icon8 = QIcon()
icon6.addFile(u":/icons/icons/document-save.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon8.addFile(u":/icons/icons/document-save.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionSave.setIcon(icon6) self.actionSave.setIcon(icon8)
self.actionSaveAs = QAction(MainWindow) self.actionSaveAs = QAction(MainWindow)
self.actionSaveAs.setObjectName(u"actionSaveAs") self.actionSaveAs.setObjectName(u"actionSaveAs")
icon7 = QIcon() icon9 = QIcon()
icon7.addFile(u":/icons/icons/document-save-as.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon9.addFile(u":/icons/icons/document-save-as.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionSaveAs.setIcon(icon7) self.actionSaveAs.setIcon(icon9)
self.actionExit = QAction(MainWindow) self.actionExit = QAction(MainWindow)
self.actionExit.setObjectName(u"actionExit") self.actionExit.setObjectName(u"actionExit")
self.actionClose = QAction(MainWindow) self.actionClose = QAction(MainWindow)
self.actionClose.setObjectName(u"actionClose") self.actionClose.setObjectName(u"actionClose")
self.actionUndo = QAction(MainWindow) self.actionUndo = QAction(MainWindow)
self.actionUndo.setObjectName(u"actionUndo") self.actionUndo.setObjectName(u"actionUndo")
icon8 = QIcon() icon10 = QIcon()
icon8.addFile(u":/icons/icons/edit-undo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon10.addFile(u":/icons/icons/edit-undo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionUndo.setIcon(icon8) self.actionUndo.setIcon(icon10)
self.actionRedo = QAction(MainWindow) self.actionRedo = QAction(MainWindow)
self.actionRedo.setObjectName(u"actionRedo") self.actionRedo.setObjectName(u"actionRedo")
icon9 = QIcon() icon11 = QIcon()
icon9.addFile(u":/icons/icons/edit-redo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon11.addFile(u":/icons/icons/edit-redo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionRedo.setIcon(icon9) self.actionRedo.setIcon(icon11)
self.actionCut = QAction(MainWindow) self.actionCut = QAction(MainWindow)
self.actionCut.setObjectName(u"actionCut") self.actionCut.setObjectName(u"actionCut")
icon10 = QIcon() icon12 = QIcon()
icon10.addFile(u":/icons/icons/edit-cut.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon12.addFile(u":/icons/icons/edit-cut.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionCut.setIcon(icon10) self.actionCut.setIcon(icon12)
self.actionCopy = QAction(MainWindow) self.actionCopy = QAction(MainWindow)
self.actionCopy.setObjectName(u"actionCopy") self.actionCopy.setObjectName(u"actionCopy")
icon11 = QIcon() icon13 = QIcon()
icon11.addFile(u":/icons/icons/edit-copy.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon13.addFile(u":/icons/icons/edit-copy.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionCopy.setIcon(icon11) self.actionCopy.setIcon(icon13)
self.actionPaste = QAction(MainWindow) self.actionPaste = QAction(MainWindow)
self.actionPaste.setObjectName(u"actionPaste") self.actionPaste.setObjectName(u"actionPaste")
icon12 = QIcon() icon14 = QIcon()
icon12.addFile(u":/icons/icons/edit-paste.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon14.addFile(u":/icons/icons/edit-paste.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionPaste.setIcon(icon12) self.actionPaste.setIcon(icon14)
self.actionSelectAll = QAction(MainWindow) self.actionSelectAll = QAction(MainWindow)
self.actionSelectAll.setObjectName(u"actionSelectAll") self.actionSelectAll.setObjectName(u"actionSelectAll")
self.actionDelete = QAction(MainWindow) self.actionDelete = QAction(MainWindow)
@@ -191,18 +201,18 @@ class Ui_MainWindow(object):
self.workspaceHeaderLayout.setContentsMargins(6, 2, 6, 2) self.workspaceHeaderLayout.setContentsMargins(6, 2, 6, 2)
self.navigateUpButton = QToolButton(self.workspaceHeader) self.navigateUpButton = QToolButton(self.workspaceHeader)
self.navigateUpButton.setObjectName(u"navigateUpButton") self.navigateUpButton.setObjectName(u"navigateUpButton")
icon13 = QIcon() icon15 = QIcon()
icon13.addFile(u":/icons/icons/arrow-up.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon15.addFile(u":/icons/icons/arrow-up.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.navigateUpButton.setIcon(icon13) self.navigateUpButton.setIcon(icon15)
self.workspaceHeaderLayout.addWidget(self.navigateUpButton) self.workspaceHeaderLayout.addWidget(self.navigateUpButton)
self.navigateDownButton = QToolButton(self.workspaceHeader) self.navigateDownButton = QToolButton(self.workspaceHeader)
self.navigateDownButton.setObjectName(u"navigateDownButton") self.navigateDownButton.setObjectName(u"navigateDownButton")
self.navigateDownButton.setEnabled(False) self.navigateDownButton.setEnabled(False)
icon14 = QIcon() icon16 = QIcon()
icon14.addFile(u":/icons/icons/arrow-down.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon16.addFile(u":/icons/icons/arrow-down.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.navigateDownButton.setIcon(icon14) self.navigateDownButton.setIcon(icon16)
self.workspaceHeaderLayout.addWidget(self.navigateDownButton) self.workspaceHeaderLayout.addWidget(self.navigateDownButton)
@@ -222,9 +232,9 @@ class Ui_MainWindow(object):
self.pointerToolButton = QToolButton(self.workspaceHeader) self.pointerToolButton = QToolButton(self.workspaceHeader)
self.pointerToolButton.setObjectName(u"pointerToolButton") self.pointerToolButton.setObjectName(u"pointerToolButton")
icon15 = QIcon() icon17 = QIcon()
icon15.addFile(u":/icons/icons/edit-select.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon17.addFile(u":/icons/icons/edit-select.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.pointerToolButton.setIcon(icon15) self.pointerToolButton.setIcon(icon17)
self.pointerToolButton.setCheckable(True) self.pointerToolButton.setCheckable(True)
self.pointerToolButton.setChecked(True) self.pointerToolButton.setChecked(True)
@@ -232,50 +242,54 @@ class Ui_MainWindow(object):
self.connectToolButton = QToolButton(self.workspaceHeader) self.connectToolButton = QToolButton(self.workspaceHeader)
self.connectToolButton.setObjectName(u"connectToolButton") self.connectToolButton.setObjectName(u"connectToolButton")
icon16 = QIcon() icon18 = QIcon()
icon16.addFile(u":/icons/icons/network-connect.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon18.addFile(u":/icons/icons/network-connect.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.connectToolButton.setIcon(icon16) self.connectToolButton.setIcon(icon18)
self.connectToolButton.setCheckable(True) self.connectToolButton.setCheckable(True)
self.workspaceHeaderLayout.addWidget(self.connectToolButton) self.workspaceHeaderLayout.addWidget(self.connectToolButton)
self.boxToolButton = QToolButton(self.workspaceHeader) self.boxToolButton = QToolButton(self.workspaceHeader)
self.boxToolButton.setObjectName(u"boxToolButton") self.boxToolButton.setObjectName(u"boxToolButton")
icon17 = QIcon() icon19 = QIcon()
icon17.addFile(u":/icons/icons/draw-rectangle.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon19.addFile(u":/icons/icons/draw-rectangle.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.boxToolButton.setIcon(icon17) self.boxToolButton.setIcon(icon19)
self.boxToolButton.setCheckable(True) self.boxToolButton.setCheckable(True)
self.workspaceHeaderLayout.addWidget(self.boxToolButton) self.workspaceHeaderLayout.addWidget(self.boxToolButton)
self.lineToolButton = QToolButton(self.workspaceHeader) self.lineToolButton = QToolButton(self.workspaceHeader)
self.lineToolButton.setObjectName(u"lineToolButton") self.lineToolButton.setObjectName(u"lineToolButton")
icon18 = QIcon() icon20 = QIcon()
icon18.addFile(u":/icons/icons/draw-bezier-curves.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon20.addFile(u":/icons/icons/draw-bezier-curves.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.lineToolButton.setIcon(icon18) self.lineToolButton.setIcon(icon20)
self.lineToolButton.setCheckable(True) self.lineToolButton.setCheckable(True)
self.workspaceHeaderLayout.addWidget(self.lineToolButton) self.workspaceHeaderLayout.addWidget(self.lineToolButton)
self.textToolButton = QToolButton(self.workspaceHeader) self.textToolButton = QToolButton(self.workspaceHeader)
self.textToolButton.setObjectName(u"textToolButton") self.textToolButton.setObjectName(u"textToolButton")
icon19 = QIcon() icon21 = QIcon()
icon19.addFile(u":/icons/icons/draw-text.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon21.addFile(u":/icons/icons/draw-text.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.textToolButton.setIcon(icon19) self.textToolButton.setIcon(icon21)
self.textToolButton.setCheckable(True) self.textToolButton.setCheckable(True)
self.workspaceHeaderLayout.addWidget(self.textToolButton) self.workspaceHeaderLayout.addWidget(self.textToolButton)
self.rotateToolButton = QToolButton(self.workspaceHeader) self.rotateToolButton = QToolButton(self.workspaceHeader)
self.rotateToolButton.setObjectName(u"rotateToolButton") self.rotateToolButton.setObjectName(u"rotateToolButton")
self.rotateToolButton.setIcon(icon1) self.rotateToolButton.setIcon(icon3)
self.workspaceHeaderLayout.addWidget(self.rotateToolButton) self.workspaceHeaderLayout.addWidget(self.rotateToolButton)
self.workspaceEditorLayout.addWidget(self.workspaceHeader) self.workspaceEditorLayout.addWidget(self.workspaceHeader)
self.workspaceStack = QStackedWidget(self.workspace) self.workspaceVerticalSplitter = QSplitter(self.workspace)
self.workspaceVerticalSplitter.setObjectName(u"workspaceVerticalSplitter")
self.workspaceVerticalSplitter.setOrientation(Qt.Orientation.Vertical)
self.workspaceVerticalSplitter.setChildrenCollapsible(False)
self.workspaceStack = QStackedWidget(self.workspaceVerticalSplitter)
self.workspaceStack.setObjectName(u"workspaceStack") self.workspaceStack.setObjectName(u"workspaceStack")
self.graphPage = QWidget() self.graphPage = QWidget()
self.graphPage.setObjectName(u"graphPage") self.graphPage.setObjectName(u"graphPage")
@@ -312,8 +326,26 @@ class Ui_MainWindow(object):
self.emptyPageLayout.addWidget(self.emptyWorkspaceLabel) self.emptyPageLayout.addWidget(self.emptyWorkspaceLabel)
self.workspaceStack.addWidget(self.emptyPage) self.workspaceStack.addWidget(self.emptyPage)
self.workspaceVerticalSplitter.addWidget(self.workspaceStack)
self.outputTabs = QTabWidget(self.workspaceVerticalSplitter)
self.outputTabs.setObjectName(u"outputTabs")
self.outputTabs.setMinimumSize(QSize(0, 100))
self.logTab = QWidget()
self.logTab.setObjectName(u"logTab")
self.logTabLayout = QVBoxLayout(self.logTab)
self.logTabLayout.setObjectName(u"logTabLayout")
self.logTabLayout.setContentsMargins(0, 0, 0, 0)
self.logOutput = QPlainTextEdit(self.logTab)
self.logOutput.setObjectName(u"logOutput")
self.logOutput.setReadOnly(True)
self.logOutput.setMaximumBlockCount(5000)
self.workspaceEditorLayout.addWidget(self.workspaceStack) self.logTabLayout.addWidget(self.logOutput)
self.outputTabs.addTab(self.logTab, "")
self.workspaceVerticalSplitter.addWidget(self.outputTabs)
self.workspaceEditorLayout.addWidget(self.workspaceVerticalSplitter)
self.workspaceSplitter.addWidget(self.workspace) self.workspaceSplitter.addWidget(self.workspace)
@@ -353,6 +385,10 @@ class Ui_MainWindow(object):
self.cameraToolbar = QToolBar(MainWindow) self.cameraToolbar = QToolBar(MainWindow)
self.cameraToolbar.setObjectName(u"cameraToolbar") self.cameraToolbar.setObjectName(u"cameraToolbar")
MainWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.cameraToolbar) MainWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.cameraToolbar)
self.simulationToolbar = QToolBar(MainWindow)
self.simulationToolbar.setObjectName(u"simulationToolbar")
self.simulationToolbar.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
MainWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.simulationToolbar)
self.menubar.addAction(self.menuFile.menuAction()) self.menubar.addAction(self.menuFile.menuAction())
self.menubar.addAction(self.menuEdit.menuAction()) self.menubar.addAction(self.menuEdit.menuAction())
@@ -393,6 +429,8 @@ class Ui_MainWindow(object):
self.cameraToolbar.addAction(self.actionZoomIn) self.cameraToolbar.addAction(self.actionZoomIn)
self.cameraToolbar.addAction(self.actionZoomOut) self.cameraToolbar.addAction(self.actionZoomOut)
self.cameraToolbar.addAction(self.actionCenterView) self.cameraToolbar.addAction(self.actionCenterView)
self.simulationToolbar.addAction(self.actionSimulationSettings)
self.simulationToolbar.addAction(self.actionCompile)
self.retranslateUi(MainWindow) self.retranslateUi(MainWindow)
@@ -404,6 +442,14 @@ class Ui_MainWindow(object):
def retranslateUi(self, MainWindow): def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"BEdit", None)) MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"BEdit", None))
self.actionSimulationSettings.setText(QCoreApplication.translate("MainWindow", u"Simulation Settings", None))
#if QT_CONFIG(statustip)
self.actionSimulationSettings.setStatusTip(QCoreApplication.translate("MainWindow", u"Edit settings stored in the active graph", None))
#endif // QT_CONFIG(statustip)
self.actionCompile.setText(QCoreApplication.translate("MainWindow", u"Compile", None))
#if QT_CONFIG(statustip)
self.actionCompile.setStatusTip(QCoreApplication.translate("MainWindow", u"Compile the active graph for simulation", None))
#endif // QT_CONFIG(statustip)
self.actionNew.setText(QCoreApplication.translate("MainWindow", u"&New", None)) self.actionNew.setText(QCoreApplication.translate("MainWindow", u"&New", None))
#if QT_CONFIG(statustip) #if QT_CONFIG(statustip)
self.actionNew.setStatusTip(QCoreApplication.translate("MainWindow", u"Create a new document", None)) self.actionNew.setStatusTip(QCoreApplication.translate("MainWindow", u"Create a new document", None))
@@ -528,6 +574,8 @@ class Ui_MainWindow(object):
#endif // QT_CONFIG(tooltip) #endif // QT_CONFIG(tooltip)
self.rotateToolButton.setText(QCoreApplication.translate("MainWindow", u"Rotate", None)) self.rotateToolButton.setText(QCoreApplication.translate("MainWindow", u"Rotate", None))
self.emptyWorkspaceLabel.setText(QCoreApplication.translate("MainWindow", u"No document open", None)) self.emptyWorkspaceLabel.setText(QCoreApplication.translate("MainWindow", u"No document open", None))
self.logOutput.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Application messages appear here.", None))
self.outputTabs.setTabText(self.outputTabs.indexOf(self.logTab), QCoreApplication.translate("MainWindow", u"Log", None))
self.menuFile.setTitle(QCoreApplication.translate("MainWindow", u"&File", None)) self.menuFile.setTitle(QCoreApplication.translate("MainWindow", u"&File", None))
self.menuEdit.setTitle(QCoreApplication.translate("MainWindow", u"&Edit", None)) self.menuEdit.setTitle(QCoreApplication.translate("MainWindow", u"&Edit", None))
self.menuView.setTitle(QCoreApplication.translate("MainWindow", u"&View", None)) self.menuView.setTitle(QCoreApplication.translate("MainWindow", u"&View", None))
@@ -537,5 +585,6 @@ class Ui_MainWindow(object):
self.fileToolbar.setWindowTitle(QCoreApplication.translate("MainWindow", u"File", None)) self.fileToolbar.setWindowTitle(QCoreApplication.translate("MainWindow", u"File", None))
self.editToolbar.setWindowTitle(QCoreApplication.translate("MainWindow", u"Edit", None)) self.editToolbar.setWindowTitle(QCoreApplication.translate("MainWindow", u"Edit", None))
self.cameraToolbar.setWindowTitle(QCoreApplication.translate("MainWindow", u"Camera", None)) self.cameraToolbar.setWindowTitle(QCoreApplication.translate("MainWindow", u"Camera", None))
self.simulationToolbar.setWindowTitle(QCoreApplication.translate("MainWindow", u"Simulation", None))
# retranslateUi # retranslateUi

View File

@@ -176,7 +176,7 @@ class Ui_SettingsDialog(object):
self.iconGridLabel.setText(QCoreApplication.translate("SettingsDialog", u"Icon grid size:", None)) self.iconGridLabel.setText(QCoreApplication.translate("SettingsDialog", u"Icon grid size:", None))
self.iconGridSpinBox.setSuffix(QCoreApplication.translate("SettingsDialog", u" units", None)) self.iconGridSpinBox.setSuffix(QCoreApplication.translate("SettingsDialog", u" units", None))
self.settingsTabs.setTabText(self.settingsTabs.indexOf(self.generalTab), QCoreApplication.translate("SettingsDialog", u"General", None)) self.settingsTabs.setTabText(self.settingsTabs.indexOf(self.generalTab), QCoreApplication.translate("SettingsDialog", u"General", None))
self.libraryPathsLabel.setText(QCoreApplication.translate("SettingsDialog", u"Load library JSON files from these files or folders at startup:", None)) self.libraryPathsLabel.setText(QCoreApplication.translate("SettingsDialog", u"Load library JSON or BEdit Binary files from these files or folders at startup:", None))
self.addLibraryFileButton.setText(QCoreApplication.translate("SettingsDialog", u"Add File\u2026", None)) self.addLibraryFileButton.setText(QCoreApplication.translate("SettingsDialog", u"Add File\u2026", None))
self.addLibraryFolderButton.setText(QCoreApplication.translate("SettingsDialog", u"Add Folder\u2026", None)) self.addLibraryFolderButton.setText(QCoreApplication.translate("SettingsDialog", u"Add Folder\u2026", None))
self.removeLibraryPathButton.setText(QCoreApplication.translate("SettingsDialog", u"Remove", None)) self.removeLibraryPathButton.setText(QCoreApplication.translate("SettingsDialog", u"Remove", None))

View File

@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'simulation_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, QCheckBox, QDialog,
QDialogButtonBox, QGroupBox, QSizePolicy, QSpacerItem,
QVBoxLayout, QWidget)
class Ui_SimulationSettingsDialog(object):
def setupUi(self, SimulationSettingsDialog):
if not SimulationSettingsDialog.objectName():
SimulationSettingsDialog.setObjectName(u"SimulationSettingsDialog")
SimulationSettingsDialog.resize(420, 180)
self.dialogLayout = QVBoxLayout(SimulationSettingsDialog)
self.dialogLayout.setObjectName(u"dialogLayout")
self.settingsGroup = QGroupBox(SimulationSettingsDialog)
self.settingsGroup.setObjectName(u"settingsGroup")
self.settingsLayout = QVBoxLayout(self.settingsGroup)
self.settingsLayout.setObjectName(u"settingsLayout")
self.placeholderCheckBox = QCheckBox(self.settingsGroup)
self.placeholderCheckBox.setObjectName(u"placeholderCheckBox")
self.settingsLayout.addWidget(self.placeholderCheckBox)
self.settingsSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
self.settingsLayout.addItem(self.settingsSpacer)
self.dialogLayout.addWidget(self.settingsGroup)
self.buttonBox = QDialogButtonBox(SimulationSettingsDialog)
self.buttonBox.setObjectName(u"buttonBox")
self.buttonBox.setStandardButtons(QDialogButtonBox.StandardButton.Cancel|QDialogButtonBox.StandardButton.Ok)
self.dialogLayout.addWidget(self.buttonBox)
self.retranslateUi(SimulationSettingsDialog)
self.buttonBox.accepted.connect(SimulationSettingsDialog.accept)
self.buttonBox.rejected.connect(SimulationSettingsDialog.reject)
QMetaObject.connectSlotsByName(SimulationSettingsDialog)
# setupUi
def retranslateUi(self, SimulationSettingsDialog):
SimulationSettingsDialog.setWindowTitle(QCoreApplication.translate("SimulationSettingsDialog", u"Simulation Settings", None))
self.settingsGroup.setTitle(QCoreApplication.translate("SimulationSettingsDialog", u"Settings", None))
self.placeholderCheckBox.setText(QCoreApplication.translate("SimulationSettingsDialog", u"Enable simulation option", None))
# retranslateUi

View File

@@ -13,7 +13,9 @@ from PySide6.QtWidgets import (
) )
from bedit.core.model import Component, Parameter from bedit.core.model import Component, Parameter
from bedit.core.serializer import JsonDocumentSerializer from bedit.core.application_log import get_logger
from bedit.core.serializer import DocumentSerializer
from bedit.core.simulation import Simulation
from bedit.gui.controllers.document import DocumentController from bedit.gui.controllers.document import DocumentController
from bedit.gui.dialogs.component_options import ComponentOptionsDialog from bedit.gui.dialogs.component_options import ComponentOptionsDialog
from bedit.gui.dialogs.item_options import ItemOptionsDialog from bedit.gui.dialogs.item_options import ItemOptionsDialog
@@ -27,8 +29,10 @@ from bedit.gui.models.library_tree import (
) )
from bedit.gui.dialogs.settings import SettingsDialog from bedit.gui.dialogs.settings import SettingsDialog
from bedit.gui.dialogs.port_options import PortOptionsDialog from bedit.gui.dialogs.port_options import PortOptionsDialog
from bedit.gui.dialogs.simulation_settings import SimulationSettingsDialog
from bedit.gui.preferences import application_settings from bedit.gui.preferences import application_settings
from bedit.gui.generated.ui_main_window import Ui_MainWindow from bedit.gui.generated.ui_main_window import Ui_MainWindow
from bedit.gui.application_log import ApplicationLogHandler
class MainWindow(QMainWindow): class MainWindow(QMainWindow):
@@ -38,11 +42,19 @@ class MainWindow(QMainWindow):
super().__init__() super().__init__()
self.ui = Ui_MainWindow() self.ui = Ui_MainWindow()
self.ui.setupUi(self) self.ui.setupUi(self)
self.application_logger = get_logger()
self.log = get_logger("ui")
self.log_handler = ApplicationLogHandler(self.ui.logOutput)
self.application_logger.addHandler(self.log_handler)
self.application_logger.setLevel("INFO")
self.ui.workspaceVerticalSplitter.setSizes([580, 160])
self.log.info("BEdit started")
self.settings = application_settings() self.settings = application_settings()
self._applying_text_definition = False self._applying_text_definition = False
self.libraries = LibraryRepository(self) self.libraries = LibraryRepository(self)
self.document_controller = DocumentController(self) self.simulation = Simulation()
self.document_controller = DocumentController(self, simulation=self.simulation)
self.library_tree_model = LibraryTreeModel( self.library_tree_model = LibraryTreeModel(
self.libraries, self.libraries,
self.document_controller, self.document_controller,
@@ -141,6 +153,10 @@ class MainWindow(QMainWindow):
self.ui.actionZoomIn.triggered.connect(self.ui.graphView.zoom_in) self.ui.actionZoomIn.triggered.connect(self.ui.graphView.zoom_in)
self.ui.actionZoomOut.triggered.connect(self.ui.graphView.zoom_out) self.ui.actionZoomOut.triggered.connect(self.ui.graphView.zoom_out)
self.ui.actionCenterView.triggered.connect(self.ui.graphView.center_workspace) self.ui.actionCenterView.triggered.connect(self.ui.graphView.center_workspace)
self.ui.actionSimulationSettings.triggered.connect(
self.show_simulation_settings
)
self.ui.actionCompile.triggered.connect(self.compile_active_graph)
self.document_controller.undo_stack.canUndoChanged.connect(self.ui.actionUndo.setEnabled) self.document_controller.undo_stack.canUndoChanged.connect(self.ui.actionUndo.setEnabled)
self.document_controller.undo_stack.canRedoChanged.connect(self.ui.actionRedo.setEnabled) self.document_controller.undo_stack.canRedoChanged.connect(self.ui.actionRedo.setEnabled)
self.document_controller.modifiedChanged.connect(lambda _modified: self._update_title()) self.document_controller.modifiedChanged.connect(lambda _modified: self._update_title())
@@ -158,19 +174,40 @@ class MainWindow(QMainWindow):
self.ui.fileToolbar, self.ui.fileToolbar,
self.ui.editToolbar, self.ui.editToolbar,
self.ui.cameraToolbar, self.ui.cameraToolbar,
self.ui.simulationToolbar,
): ):
self.ui.menuToolbars.addAction(toolbar.toggleViewAction()) self.ui.menuToolbars.addAction(toolbar.toggleViewAction())
def reload_libraries(self) -> None: def reload_libraries(self) -> None:
self.libraries.load_paths(SettingsDialog.library_paths(self.settings)) self.libraries.load_paths(SettingsDialog.library_paths(self.settings))
self.ui.treeView.expandAll() self.ui.treeView.expandAll()
self.log.info("Reloaded %d libraries", len(self.libraries.libraries))
if self.libraries.load_warnings: if self.libraries.load_warnings:
for warning in self.libraries.load_warnings:
self.log.warning("Library load failed: %s", warning)
QMessageBox.warning( QMessageBox.warning(
self, self,
"Some libraries could not be loaded", "Some libraries could not be loaded",
"\n".join(self.libraries.load_warnings), "\n".join(self.libraries.load_warnings),
) )
@Slot()
def show_simulation_settings(self) -> None:
component = self.document_controller.active_component
if component is None or component.implementation_kind != "graph":
return
dialog = SimulationSettingsDialog(component.graph.simulation_settings, self)
if dialog.exec() == dialog.DialogCode.Accepted:
self.document_controller.edit_simulation_settings(dialog.settings)
@Slot()
def compile_active_graph(self) -> None:
try:
self.document_controller.compile_active_graph()
except ValueError as error:
self.log.error("Compile failed: %s", error)
QMessageBox.warning(self, "Cannot compile", str(error))
def _restore_window_geometry(self) -> None: def _restore_window_geometry(self) -> None:
geometry = self.settings.value("window/geometry") geometry = self.settings.value("window/geometry")
if geometry is not None: if geometry is not None:
@@ -196,6 +233,8 @@ class MainWindow(QMainWindow):
self.ui.workspaceModeLabel.setText("") self.ui.workspaceModeLabel.setText("")
self.ui.workspaceStack.setCurrentWidget(self.ui.emptyPage) self.ui.workspaceStack.setCurrentWidget(self.ui.emptyPage)
self._set_graph_controls_visible(False) self._set_graph_controls_visible(False)
self.ui.actionSimulationSettings.setEnabled(False)
self.ui.actionCompile.setEnabled(False)
self._update_edit_actions() self._update_edit_actions()
return return
self.ui.graphBreadcrumbLabel.setText(" ".join(self.document_controller.breadcrumb())) self.ui.graphBreadcrumbLabel.setText(" ".join(self.document_controller.breadcrumb()))
@@ -203,6 +242,8 @@ class MainWindow(QMainWindow):
self.document_controller.document.find_parent(component.id) is not None self.document_controller.document.find_parent(component.id) is not None
) )
is_graph = component.implementation_kind == "graph" is_graph = component.implementation_kind == "graph"
self.ui.actionSimulationSettings.setEnabled(is_graph)
self.ui.actionCompile.setEnabled(is_graph)
self.ui.workspaceModeLabel.setText("Graph" if is_graph else "Text") self.ui.workspaceModeLabel.setText("Graph" if is_graph else "Text")
self.ui.workspaceStack.setCurrentWidget( self.ui.workspaceStack.setCurrentWidget(
self.ui.graphPage if is_graph else self.ui.textPage self.ui.graphPage if is_graph else self.ui.textPage
@@ -314,6 +355,7 @@ class MainWindow(QMainWindow):
finally: finally:
self._applying_text_definition = False self._applying_text_definition = False
except (TypeError, ValueError) as error: except (TypeError, ValueError) as error:
self.log.error("Text component update failed: %s", error)
QMessageBox.critical(self, "Invalid text component", str(error)) QMessageBox.critical(self, "Invalid text component", str(error))
self._load_text_definition() self._load_text_definition()
return False return False
@@ -351,24 +393,33 @@ class MainWindow(QMainWindow):
def new_document(self) -> None: def new_document(self) -> None:
if self._resolve_source_edits() and self._maybe_save(): if self._resolve_source_edits() and self._maybe_save():
self.document_controller.new_document() self.document_controller.new_document()
self.log.info("Created a new document")
@Slot() @Slot()
def close_document(self) -> None: def close_document(self) -> None:
if self._resolve_source_edits() and self._maybe_save(): if self._resolve_source_edits() and self._maybe_save():
path = self.document_controller.file_path
self.document_controller.close_document() self.document_controller.close_document()
self.log.info("Closed document%s", f" {path}" if path else "")
@Slot() @Slot()
def open_document(self) -> None: def open_document(self) -> None:
if not self._resolve_source_edits() or not self._maybe_save(): if not self._resolve_source_edits() or not self._maybe_save():
return return
filename, _ = QFileDialog.getOpenFileName( filename, _ = QFileDialog.getOpenFileName(
self, "Open graph", "", "BEdit graphs (*.bedit.json *.json);;All files (*)" self,
"Open graph",
"",
"BEdit documents (*.bedit.json *.json *.beb);;"
"BEdit JSON (*.bedit.json *.json);;BEdit Binary (*.beb);;All files (*)",
) )
if not filename: if not filename:
return return
try: try:
self.document_controller.load(Path(filename)) self.document_controller.load(Path(filename))
self.log.info("Opened document %s", filename)
except (OSError, ValueError) as error: except (OSError, ValueError) as error:
self.log.error("Could not open document %s: %s", filename, error)
QMessageBox.critical(self, "Could not open graph", str(error)) QMessageBox.critical(self, "Could not open graph", str(error))
@Slot() @Slot()
@@ -378,8 +429,10 @@ class MainWindow(QMainWindow):
if self.document_controller.file_path is None: if self.document_controller.file_path is None:
return self.save_document_as() return self.save_document_as()
try: try:
self.document_controller.save() path = self.document_controller.save()
self.log.info("Saved document %s", path)
except OSError as error: except OSError as error:
self.log.error("Could not save document: %s", error)
QMessageBox.critical(self, "Could not save graph", str(error)) QMessageBox.critical(self, "Could not save graph", str(error))
return False return False
return True return True
@@ -388,17 +441,24 @@ class MainWindow(QMainWindow):
def save_document_as(self) -> bool: def save_document_as(self) -> bool:
if self.document_controller.document is None: if self.document_controller.document is None:
return False return False
filename, _ = QFileDialog.getSaveFileName( filename, selected_filter = QFileDialog.getSaveFileName(
self, self,
"Save graph", "Save graph",
"untitled.bedit.json", "untitled.beb",
"BEdit graphs (*.bedit.json);;JSON files (*.json);;All files (*)", "BEdit Binary (*.beb);;BEdit JSON (*.bedit.json *.json);;All files (*)",
) )
if not filename: if not filename:
return False return False
path = Path(filename)
if path.suffix.lower() not in {".json", ".beb"}:
path = path.with_suffix(
".beb" if "Binary" in selected_filter else ".bedit.json"
)
try: try:
self.document_controller.save(Path(filename)) path = self.document_controller.save(path)
self.log.info("Saved document %s", path)
except OSError as error: except OSError as error:
self.log.error("Could not save document %s: %s", filename, error)
QMessageBox.critical(self, "Could not save graph", str(error)) QMessageBox.critical(self, "Could not save graph", str(error))
return False return False
return True return True
@@ -506,9 +566,10 @@ class MainWindow(QMainWindow):
try: try:
if library is not None: if library is not None:
library.document.validate() library.document.validate()
JsonDocumentSerializer.save(library.document, Path(library.source_path)) DocumentSerializer.save(library.document, Path(library.source_path))
except (OSError, ValueError) as error: except (OSError, ValueError) as error:
component.inputs, component.outputs = old_inputs, old_outputs component.inputs, component.outputs = old_inputs, old_outputs
self.log.error("Could not change library ports: %s", error)
QMessageBox.warning(self, "Cannot change library ports", str(error)) QMessageBox.warning(self, "Cannot change library ports", str(error))
self.library_tree_model.rebuild() self.library_tree_model.rebuild()
@@ -526,6 +587,7 @@ class MainWindow(QMainWindow):
component_id, dialog.inputs, dialog.outputs component_id, dialog.inputs, dialog.outputs
) )
except ValueError as error: except ValueError as error:
self.log.error("Could not change component ports: %s", error)
QMessageBox.warning(self, "Cannot change ports", str(error)) QMessageBox.warning(self, "Cannot change ports", str(error))
@Slot(str) @Slot(str)
@@ -547,6 +609,7 @@ class MainWindow(QMainWindow):
dialog.ui.showNameCheckBox.isChecked(), dialog.ui.showNameCheckBox.isChecked(),
) )
except ValueError as error: except ValueError as error:
self.log.error("Could not rename component: %s", error)
QMessageBox.warning(self, "Cannot rename component", str(error)) QMessageBox.warning(self, "Cannot rename component", str(error))
@Slot(str, str) @Slot(str, str)
@@ -595,4 +658,6 @@ class MainWindow(QMainWindow):
event.ignore() event.ignore()
return return
self.settings.setValue("window/geometry", self.saveGeometry()) self.settings.setValue("window/geometry", self.saveGeometry())
self.log.info("BEdit closed")
self.application_logger.removeHandler(self.log_handler)
event.accept() event.accept()

View File

@@ -1,4 +1,3 @@
import json
from pathlib import Path from pathlib import Path
from PySide6.QtCore import QObject, Signal from PySide6.QtCore import QObject, Signal
@@ -23,7 +22,7 @@ class LibraryRepository(QObject):
for candidate in library_candidates(path): for candidate in library_candidates(path):
try: try:
libraries.append(load_library_file(candidate)) libraries.append(load_library_file(candidate))
except (OSError, ValueError, KeyError, TypeError, json.JSONDecodeError) as error: except (OSError, ValueError, KeyError, TypeError) as error:
warnings.append(f"{candidate}: {error}") warnings.append(f"{candidate}: {error}")
self.libraries = libraries self.libraries = libraries
self.load_warnings = warnings self.load_warnings = warnings

View File

@@ -364,7 +364,10 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QStackedWidget" name="workspaceStack"> <widget class="QSplitter" name="workspaceVerticalSplitter">
<property name="orientation"><enum>Qt::Orientation::Vertical</enum></property>
<property name="childrenCollapsible"><bool>false</bool></property>
<widget class="QStackedWidget" name="workspaceStack">
<property name="currentIndex"> <property name="currentIndex">
<number>0</number> <number>0</number>
</property> </property>
@@ -426,6 +429,20 @@
</item> </item>
</layout> </layout>
</widget> </widget>
</widget>
<widget class="QTabWidget" name="outputTabs">
<property name="minimumSize"><size><width>0</width><height>100</height></size></property>
<widget class="QWidget" name="logTab">
<attribute name="title"><string>Log</string></attribute>
<layout class="QVBoxLayout" name="logTabLayout">
<property name="leftMargin"><number>0</number></property>
<property name="topMargin"><number>0</number></property>
<property name="rightMargin"><number>0</number></property>
<property name="bottomMargin"><number>0</number></property>
<item><widget class="QPlainTextEdit" name="logOutput"><property name="readOnly"><bool>true</bool></property><property name="maximumBlockCount"><number>5000</number></property><property name="placeholderText"><string>Application messages appear here.</string></property></widget></item>
</layout>
</widget>
</widget>
</widget> </widget>
</item> </item>
</layout> </layout>
@@ -570,6 +587,46 @@
<addaction name="actionZoomOut"/> <addaction name="actionZoomOut"/>
<addaction name="actionCenterView"/> <addaction name="actionCenterView"/>
</widget> </widget>
<widget class="QToolBar" name="simulationToolbar">
<property name="windowTitle">
<string>Simulation</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonStyle::ToolButtonIconOnly</enum>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionSimulationSettings"/>
<addaction name="actionCompile"/>
</widget>
<action name="actionSimulationSettings">
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/icons/icons/preferences-system.png</normaloff>:/icons/icons/preferences-system.png</iconset>
</property>
<property name="text">
<string>Simulation Settings</string>
</property>
<property name="statusTip">
<string>Edit settings stored in the active graph</string>
</property>
</action>
<action name="actionCompile">
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/icons/icons/run-build.png</normaloff>:/icons/icons/run-build.png</iconset>
</property>
<property name="text">
<string>Compile</string>
</property>
<property name="statusTip">
<string>Compile the active graph for simulation</string>
</property>
</action>
<action name="actionNew"> <action name="actionNew">
<property name="icon"> <property name="icon">
<iconset resource="../resources/resources.qrc"> <iconset resource="../resources/resources.qrc">

View File

@@ -94,7 +94,7 @@
<item> <item>
<widget class="QLabel" name="libraryPathsLabel"> <widget class="QLabel" name="libraryPathsLabel">
<property name="text"> <property name="text">
<string>Load library JSON files from these files or folders at startup:</string> <string>Load library JSON or BEdit Binary files from these files or folders at startup:</string>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SimulationSettingsDialog</class>
<widget class="QDialog" name="SimulationSettingsDialog">
<property name="geometry"><rect><x>0</x><y>0</y><width>420</width><height>180</height></rect></property>
<property name="windowTitle"><string>Simulation Settings</string></property>
<layout class="QVBoxLayout" name="dialogLayout">
<item><widget class="QGroupBox" name="settingsGroup"><property name="title"><string>Settings</string></property><layout class="QVBoxLayout" name="settingsLayout"><item><widget class="QCheckBox" name="placeholderCheckBox"><property name="text"><string>Enable simulation option</string></property></widget></item><item><spacer name="settingsSpacer"><property name="orientation"><enum>Qt::Orientation::Vertical</enum></property><property name="sizeHint" stdset="0"><size><width>20</width><height>40</height></size></property></spacer></item></layout></widget></item>
<item><widget class="QDialogButtonBox" name="buttonBox"><property name="standardButtons"><set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set></property></widget></item>
</layout>
</widget>
<resources/>
<connections>
<connection><sender>buttonBox</sender><signal>accepted()</signal><receiver>SimulationSettingsDialog</receiver><slot>accept()</slot><hints/></connection>
<connection><sender>buttonBox</sender><signal>rejected()</signal><receiver>SimulationSettingsDialog</receiver><slot>reject()</slot><hints/></connection>
</connections>
</ui>

View File

@@ -908,7 +908,10 @@
}, },
"type": "signal" "type": "signal"
} }
] ],
"simulation": {
"enabled": true
}
} }
} }
} }