mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-04-09 10:10:55 +02:00
Compare commits
27 Commits
pre_releas
...
fix/wavefo
| Author | SHA1 | Date | |
|---|---|---|---|
| 15eebd500b | |||
|
|
bc0e277332 | ||
| 75a2780fe0 | |||
| a6c479e42e | |||
| 64a4824054 | |||
| 1619446ec9 | |||
| 37f002427a | |||
|
|
50cb70dcc6 | ||
| 55f7efc4f5 | |||
| be72c9f270 | |||
| c8cedc0124 | |||
|
|
3fdbe4031e | ||
| c16b9dce9c | |||
| 9387275851 | |||
| 94463afdba | |||
| 02563b10f3 | |||
| fff4af2489 | |||
| 452124b528 | |||
|
|
9c84e158ba | ||
| 58a0bc7974 | |||
| 770dbd4b63 | |||
| d22035f897 | |||
|
|
fe21b39b7f | ||
| 1b78840fd8 | |||
|
|
46519342b6 | ||
| 9079ddd727 | |||
|
|
205745cc72 |
@@ -13,7 +13,7 @@ variables:
|
||||
value: main
|
||||
CHILD_PIPELINE_BRANCH: $CI_DEFAULT_BRANCH
|
||||
CHECK_PKG_VERSIONS:
|
||||
description: Whether to run additional tests against min/max/random selection of dependencies. Set to 1 for running.
|
||||
description: Whether to run additional tests against min/max/random selection of dependencies. Set to 1 for running.
|
||||
value: 0
|
||||
|
||||
workflow:
|
||||
@@ -77,7 +77,7 @@ formatter:
|
||||
stage: Formatter
|
||||
needs: []
|
||||
script:
|
||||
- pip install bec_lib[dev]
|
||||
- pip install -e ./[dev]
|
||||
- isort --check --diff --line-length=100 --profile=black --multi-line=3 --trailing-comma ./
|
||||
- black --check --diff --color --line-length=100 --skip-magic-trailing-comma ./
|
||||
rules:
|
||||
@@ -162,6 +162,20 @@ tests:
|
||||
- tests/reference_failures/
|
||||
when: always
|
||||
|
||||
generate-client-check:
|
||||
stage: test
|
||||
needs: []
|
||||
variables:
|
||||
QT_QPA_PLATFORM: "offscreen"
|
||||
script:
|
||||
- *clone-repos
|
||||
- *install-os-packages
|
||||
- *install-repos
|
||||
- pip install -e .[dev,pyside6]
|
||||
- bw-generate-cli --target bec_widgets
|
||||
# if there are changes in the generated files, fail the job
|
||||
- git diff --exit-code
|
||||
|
||||
test-matrix:
|
||||
parallel:
|
||||
matrix:
|
||||
@@ -189,7 +203,7 @@ test-matrix:
|
||||
end-2-end-conda:
|
||||
stage: End2End
|
||||
needs: []
|
||||
image: continuumio/miniconda3
|
||||
image: continuumio/miniconda3:25.1.1-2
|
||||
allow_failure: false
|
||||
variables:
|
||||
QT_QPA_PLATFORM: "offscreen"
|
||||
@@ -216,7 +230,7 @@ end-2-end-conda:
|
||||
- pip install -e ./ophyd_devices
|
||||
|
||||
- pip install -e .[dev,pyside6]
|
||||
- pytest -v --files-path ./ --start-servers --flush-redis --random-order ./tests/end-2-end
|
||||
- pytest -v --files-path ./ --start-servers --random-order ./tests/end-2-end
|
||||
|
||||
artifacts:
|
||||
when: on_failure
|
||||
@@ -231,7 +245,7 @@ end-2-end-conda:
|
||||
- if: '$CI_PIPELINE_SOURCE == "parent_pipeline"'
|
||||
- if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main"'
|
||||
- if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "production"'
|
||||
- if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ /^pre_release.*$/'
|
||||
- if: "$CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ /^pre_release.*$/"
|
||||
|
||||
semver:
|
||||
stage: Deploy
|
||||
|
||||
731
CHANGELOG.md
731
CHANGELOG.md
@@ -1,6 +1,737 @@
|
||||
# CHANGELOG
|
||||
|
||||
|
||||
## v2.1.2 (2025-05-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **waveform**: Ignore callbacks for on_async_readback from QtSender objects that are already
|
||||
destroyed; closes #497
|
||||
([`64a4824`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/64a48240546846fdf4541c2adf3a0a5a0829f948))
|
||||
|
||||
### Build System
|
||||
|
||||
- Remove flush-redis from ci job
|
||||
([`a6c479e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a6c479e42ea2a47c45e5a323bb3072bab503ecf1))
|
||||
|
||||
### Refactoring
|
||||
|
||||
- **bec-progressbar**: Add private method for bec_progressbar, udate client file
|
||||
([`37f0024`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/37f002427ad5da01164ae3b0f4983695fe61c243))
|
||||
|
||||
- **bec-status-box**: Add get_server_state user_access method to BECStatusBox
|
||||
([`1619446`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1619446ec9839cfa1c666a3790a0c2abc449c4a8))
|
||||
|
||||
|
||||
## v2.1.1 (2025-05-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Import add operator in client
|
||||
([`55f7efc`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/55f7efc4f586128dfb66fc6a8eb5d3a9f32bf61e))
|
||||
|
||||
### Refactoring
|
||||
|
||||
- Supply bec designer filename to function
|
||||
([`be72c9f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/be72c9f2708c93dab24d4383f5622e38cf1dc8a2))
|
||||
|
||||
|
||||
## v2.1.0 (2025-05-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Ensure rpc object do not collide with protected names
|
||||
([`94463af`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/94463afdba11fe2da5958a371ef49572889b8622))
|
||||
|
||||
### Chores
|
||||
|
||||
- **formatter**: Upgrade to black v25
|
||||
([`452124b`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/452124b528c41db14d1e34ab98db95f6f7230ad6))
|
||||
|
||||
### Continuous Integration
|
||||
|
||||
- Install dev dependencies for formatter
|
||||
([`fff4af2`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/fff4af2489bdea0cf4f6f8db68db59fba411c25e))
|
||||
|
||||
### Features
|
||||
|
||||
- **SafeSlot**: Slot parameters can be overridden with kwarg; add option to raise
|
||||
([`9387275`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/93872758517177503b1f868376a6095670131844))
|
||||
|
||||
### Refactoring
|
||||
|
||||
- **colormap_widget**: Widget is rounded
|
||||
([`02563b1`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/02563b10f3c90bddc069446dfe4137aa5a9727cb))
|
||||
|
||||
### Testing
|
||||
|
||||
- **Dock**: Add validation for new dock creation with invalid name
|
||||
([`c16b9dc`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/c16b9dce9ce629b794d731cd7f3282a59f8b8c59))
|
||||
|
||||
|
||||
## v2.0.3 (2025-05-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **generate_cli**: Apply isort config
|
||||
([`770dbd4`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/770dbd4b63baba588871a4d4ffa77d44872d085b))
|
||||
|
||||
- **image_item**: Wrong user access name for rotation
|
||||
([`58a0bc7`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/58a0bc79742e7e7578988711a9840ed6041d9a69))
|
||||
|
||||
### Continuous Integration
|
||||
|
||||
- Add job to test that the generated client is up to date
|
||||
([`d22035f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/d22035f8974ac51ae1b6efc0e2b3749ca0a674ff))
|
||||
|
||||
|
||||
## v2.0.2 (2025-05-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **plot_base**: No content margin for plot_widget window
|
||||
([`1b78840`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1b78840fd87ea0f156c73beeb57c6c06f685f7b1))
|
||||
|
||||
|
||||
## v2.0.1 (2025-04-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **dock_area**: Restore state safeguard to not pass none to pyqtgraph restoreState
|
||||
([`9079ddd`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/9079ddd7278ede7a9a12d7b39797154e83659c20))
|
||||
|
||||
|
||||
## v2.0.0 (2025-04-29)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Add designer plugin for ScanMetadata
|
||||
([`43e1aa9`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/43e1aa9505cfa6e87b4fce1d065efb48b4111190))
|
||||
|
||||
- Add support for 'add_slice', add downsampling for performance improvements. add tests
|
||||
([`7f7891d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/7f7891dfa54588f5d902448b760f141b183a7fa1))
|
||||
|
||||
- Broadcast context manager to emit registry changes just once
|
||||
([`a5f06c8`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a5f06c8f8380156a763445a69df29ee0e62e434c))
|
||||
|
||||
- Bugfix in cleanup of ScatterWaveform ScatterCurve; closes #520
|
||||
([`1d09107`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1d091071e1179821bb1dcd47fb97f3d0959b972f))
|
||||
|
||||
- Change default colormap to plasma
|
||||
([`074bbbc`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/074bbbc16648849bdfcfc28b2c520b0e38dd07c2))
|
||||
|
||||
- Create widget enum programatically
|
||||
([`7726d83`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/7726d83b6834b8145e48e709e2f839fb0ec1b971))
|
||||
|
||||
- Ensure provided dock and dock_area names are valid and defaults are snake_case
|
||||
([`0ac14a7`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/0ac14a74b851578fff668fb8c6722f990130831d))
|
||||
|
||||
- Expose common classes from bec_widgets package
|
||||
([`28ae0d2`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/28ae0d2b577d7c926ee54690898fe8e327e1229f))
|
||||
|
||||
- Forward parent to children
|
||||
([`1fc6125`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1fc61253699425a2bf64a0f8b560f8474549b841))
|
||||
|
||||
- Import from qtpy instead of PySide6
|
||||
([`fef07ac`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/fef07ac8e12399e7e49bcd673a5fc7cbf713bc50))
|
||||
|
||||
- Proper cleanup of progressbar
|
||||
([`8ff2063`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/8ff2063bc8978c5b2a97f720d5da055e8ec08f0c))
|
||||
|
||||
- Rpc access enabled for certain widgets.
|
||||
([`ef4a52c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/ef4a52cc17748f35ed627170b1025e6e028d70b8))
|
||||
|
||||
- Server shutdown widgets
|
||||
([`75b2446`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/75b24467def65284ea6b6114b25098437e31ec95))
|
||||
|
||||
- Support auto_range_x/y for viewAll during measurement
|
||||
([`af28e2e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/af28e2e433c9b0233436da850be97cd63df90a74))
|
||||
|
||||
- Unique name for widgets, fix new method for docks; closes #534
|
||||
([`77f9d42`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/77f9d425765061f137c997062a3bf769a939bc64))
|
||||
|
||||
- Warning in logpanel
|
||||
([`1d7b423`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1d7b423bb307b6aae3879987776310c14380895d))
|
||||
|
||||
- chain a signal to the child BecLogsQueue rather than passing the signal instance in
|
||||
|
||||
- Wrap fetching plugin widgets in case of errors
|
||||
([`ef14831`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/ef148317dea9c7ff985b2a3ff06ccdb37258153f))
|
||||
|
||||
- **auto_updates**: Fix condition to skip auto update
|
||||
([`18e4ba6`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/18e4ba6cfe9f67512efbd3989156de5670aab3fe))
|
||||
|
||||
- **bec_connector**: Add assertion to ensure BECConnector is used with a QObject; closes #475
|
||||
([`1921444`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1921444e152e06c4decc790452f3c496cf8ee961))
|
||||
|
||||
- **bec_connector**: Add setObjectName method to update object name and broadcast if registered;
|
||||
closes #472
|
||||
([`064343a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/064343acf2631e4ae62b2a5e08bc08087246570c))
|
||||
|
||||
- **bec_connector**: Call cleanup on widgets if the parent was deleted
|
||||
([`fc1cdc8`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/fc1cdc814fc3c44a571c20986bc627935f90ff91))
|
||||
|
||||
- **bec_connector**: Improve cleanup handling on deleted parent to prevent errors
|
||||
([`3709cdc`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3709cdc86671e5219afca7a8e11bdd01f03dd30e))
|
||||
|
||||
- **bec_connector**: Move RPC registration into single shot method to ensure the rpc name is in sync
|
||||
([`3b16c9f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3b16c9f5a2f7f16b23f25560b1e8fb4e42359ef0))
|
||||
|
||||
- **bec_queue**: Set parent for toolbar buttons
|
||||
([`cdc613b`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/cdc613b6e7d7eac806d458515321590e9344244a))
|
||||
|
||||
- **becconnector**: Widgets can be flagged as root widget, skipping the BECMainWindow in CLI usage
|
||||
([`061f348`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/061f3481daae6844a83c44e9caca7ed56a1bb100))
|
||||
|
||||
- **becconnector,widgets**: Parent_id is always fetched from the real bec widget parent; all widgets
|
||||
adjusted; hardcoded parent_ids removed
|
||||
([`f35f4c4`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f35f4c4b295139b99a2dad9e8241f900d2565aeb))
|
||||
|
||||
- **BECGuiClient**: Add launch_script parameter to dock area creation
|
||||
([`06a4954`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/06a4954d3da44c6805232d34e47e242b28ba7fd1))
|
||||
|
||||
- **cleanup**: Prevent double cleanup by tracking object destruction state
|
||||
([`fde9120`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/fde912005db61a60707e7181c3425a4557bdc011))
|
||||
|
||||
- **cli**: Add type ignore comment to generated files
|
||||
([`d171255`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/d1712552ffd1118845dc7121218df86ce10e8750))
|
||||
|
||||
- **client**: Import reduce
|
||||
([`8cca510`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/8cca510fa1cbda00a07edbef9d36fdd74e63d201))
|
||||
|
||||
- **client**: Regenerated client
|
||||
([`c97db6a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/c97db6aaae81d08019a13c344414c16c42691654))
|
||||
|
||||
- **client**: Rpc API adjusted for DockArea, ImageItem and Waveform
|
||||
([`6ca4aa0`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6ca4aa0f9b9d5ace9fb1e174219f4da5617ebbac))
|
||||
|
||||
- **client_utils**: Simplify RPC client instantiation in BECGuiClient
|
||||
([`96b31a4`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/96b31a450998aca2b7ac94138b07223418d2bacd))
|
||||
|
||||
- **colormap_widget**: Size policy fixed
|
||||
([`1cc2a98`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1cc2a9848906a7013e86687976d42d4b9676b25f))
|
||||
|
||||
- **compact_popup**: Forward close event
|
||||
([`e0f146b`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/e0f146beeb34367a4d3454a7012af4728d594b9b))
|
||||
|
||||
- **crosshair**: Adapted for 2D image
|
||||
([`a85402d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a85402dde1af1d9c4a154892c46422ac3e1f22f9))
|
||||
|
||||
- **curve**: Fix unique names for custom curves
|
||||
([`8e846d4`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/8e846d449955ded3cb8090e44ea36d26efccb80e))
|
||||
|
||||
- **dark-mode-button**: Fix parent passed to QObjects in various classes
|
||||
([`a06f060`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a06f0600c1c9a80436f01533a82905a6f3633895))
|
||||
|
||||
- **designer**: Avoid touching deleted widgets during init as QtDesigner will segfault
|
||||
([`4381fcc`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4381fcc4c212cd03ce91f1638dc361c3315f8c45))
|
||||
|
||||
- **designer-plugin-generator**: Enhance super constructor validation for new style classes
|
||||
([`6318b2d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6318b2d822be0ded561a1afd0d485158614e2406))
|
||||
|
||||
- **device_input_base**: Removed enums from Pydantic models to make them serialisable
|
||||
([`43b747e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/43b747ec8a761530d78b26650b0ec2ee4581ffaf))
|
||||
|
||||
- **dock_area**: Close BECMainWindow if dock area is central widget
|
||||
([`e725de3`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/e725de3c4504d43fbcad25d69c5cb8cbe7a70867))
|
||||
|
||||
- **docs**: Update copyright year to be dynamic
|
||||
([`f2d5b57`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f2d5b57e86d0c9d690b8d9f988035427608f0b4c))
|
||||
|
||||
- **entry_validator**: Validator reports list of signal if user chooses the wrong one
|
||||
([`da05877`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/da05877dd04fa618cdb45268cb62df602a5e808f))
|
||||
|
||||
- **image**: Imageitem remove adjusted to disconnect and remove current displayed image
|
||||
([`98f159b`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/98f159b25f6bf7e1f2dd76726d7ab66a0baf88de))
|
||||
|
||||
- **launch_window**: Redesign
|
||||
([`7e65d4f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/7e65d4f2d6d840d3895e023f5cd090a56ea6e5f3))
|
||||
|
||||
- **launch_window**: Return None when cancelling the ui file launcher
|
||||
([`b3dbe92`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b3dbe922dea2cea9190d1583bd6b69f1a45d6b90))
|
||||
|
||||
- **launch_window**: Update LaunchTile icon to use new UI loader tile image
|
||||
([`3cd6e05`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3cd6e05b2478654210049ca8e1756ad592f1da81))
|
||||
|
||||
- **launcher**: Hide launcher when launcher is closed even though it is not the last widget
|
||||
([`6e7920c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6e7920c119824650006e7357ca2f4ff95d413e13))
|
||||
|
||||
- **lmfit_dialog_vertical**: Vertical sizePolicy fixed
|
||||
([`584b945`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/584b94500565a33e5daed86b7552ec54f1135cf6))
|
||||
|
||||
- **main_window**: Connected to theme change
|
||||
([`11feeff`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/11feeff37ce0b02fcbc8e506c67c14e1fc5e0cb6))
|
||||
|
||||
- **main_window**: Show app id only when connected to redis
|
||||
([`be72268`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/be722683a7cc7b215c572f9c2e996839b010b64e))
|
||||
|
||||
- **moduar-toolbar**: Fix cleanup of modular toolbar and dock_area
|
||||
([`c70cd9d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/c70cd9d6e8f7ea9d5f81b10ac437cdcc9ee900e9))
|
||||
|
||||
- **motor_map**: Limit map creating optimized
|
||||
([`9f2a083`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/9f2a083abbcfb465ebea9acee8263dcc9a6da5d9))
|
||||
|
||||
- **plot_base**: Ability to set y label suffix
|
||||
([`890b501`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/890b50115fef845c2a77242fdb05863d2eec4a00))
|
||||
|
||||
- **plot_base**: Aspect ratio removed from the PlotBase
|
||||
([`19d8aeb`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/19d8aeb16249a1093cfec124d0ebdf6af11d94a8))
|
||||
|
||||
- **plot_base**: Axis setting filter for relevant properties
|
||||
([`0204d9c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/0204d9c86f9665dcefdcbe7f49ac23918d74dd66))
|
||||
|
||||
- **plot_base**: Do not enable inner axes when label is changed
|
||||
([`98eda03`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/98eda03f4d6d449605d5559a1db44c900d93cb79))
|
||||
|
||||
- **plot_base**: Enable popup property fixed
|
||||
([`30db183`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/30db18367e9c6d6375fda970a1bb255d966cba5a))
|
||||
|
||||
- **plot_base**: Fix cleanup of popups if popups are still open when PlotBase is closed
|
||||
([`39cf4dd`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/39cf4ddd5a033ee7f589d508f765669186e776bc))
|
||||
|
||||
- **plot_base**: Improved handling of matplotlib exporter errors
|
||||
([`4f9514f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4f9514fbd1ff0059248d3b7b5b4fcd85c3eb9c72))
|
||||
|
||||
- **plot_base**: Inner and outer axis setting in popup mode
|
||||
([`055b968`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/055b96818aa69d66119caee9a3e8c24575ce60b4))
|
||||
|
||||
- **plot_base**: Update mouse mode state on mode change
|
||||
([`fc24c8b`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/fc24c8b3a5f9cd55fb3d49f753b53a65a2a0fa26))
|
||||
|
||||
- **plot_framework**: All widgets, popups and side menus cleanups adjusted
|
||||
([`337a332`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/337a332ed123f99729b8cf6869f7fe4b056c2b16))
|
||||
|
||||
- **plot_indicators**: Cleanup adjusted
|
||||
([`4865341`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/48653410101a2a38d5067fbfca7712d255d89625))
|
||||
|
||||
- **plot_indicators**: Plot indicators added to the PlotBase
|
||||
([`42e3b9c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/42e3b9c13786e67220874a1275a3d9ee9515541a))
|
||||
|
||||
- **positioner-indicator**: Fix property setters for position indicator
|
||||
([`1910993`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1910993b2b3d30ecb8e4977b4a362f46adae3c75))
|
||||
|
||||
- **progress-ring-bar**: Fix parent inheritance and cleanup of ring objects; closes #496
|
||||
([`b460ea9`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b460ea9955318879ddfe4f9ae963249ba342bbb5))
|
||||
|
||||
- **ring-progress-bar**: Fix bug in disconnect slot of rings, enable 'scan' mode as default for init
|
||||
with first ring
|
||||
([`7c303d0`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/7c303d01294493a55fd26db8c1475e8c58b3e492))
|
||||
|
||||
- **ring_progress_bar**: Replaced hard-coded endpoints by MessageEndpoints
|
||||
([`e4e9feb`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/e4e9febc98268a4b6b9774b253419e88ea044811))
|
||||
|
||||
- **round_frame**: Orientation can be vertical
|
||||
([`c1bbb16`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/c1bbb16dad481c628e7680180d7250ba8a560c46))
|
||||
|
||||
- **round_frame**: Roundframe removed from BECWidget inheritance
|
||||
([`b58a098`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b58a098ed4afbe62721fd2bf8497f363deecbfa6))
|
||||
|
||||
- **rpc**: Call close on container widget if needed
|
||||
([`a13de45`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a13de45131309771c1438407f3733a8c0897d495))
|
||||
|
||||
- **rpc-base**: Deprecate widget_name in favor of object_name; closes #499
|
||||
([`86647b9`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/86647b9b7e2fa111105ae483808883a624fa4cd6))
|
||||
|
||||
- **rpc_base**: Ensure message wait event is set after processing RPC response
|
||||
([`4dc59aa`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4dc59aa5e9b15e5ec40401e80e7965acd88e2fce))
|
||||
|
||||
- **rpc_base**: Timeout run_rpc 3s
|
||||
([`8558b46`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/8558b46114760a9434eaa827f81d5fd9d047112f))
|
||||
|
||||
- **rpc_register**: _lock and _skip_broad_cast moved to instance attributes
|
||||
([`8d17f7e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/8d17f7e32f81894294d7da472268e8d9eb3bb74b))
|
||||
|
||||
- **rpc_register**: Change add_rpc parameter type to BECConnector and add object_is_registered
|
||||
method
|
||||
([`82b8265`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/82b82659b7919b15d629375866302624b5b6e457))
|
||||
|
||||
- **rpc_register**: Lock changed to RLock
|
||||
([`6c90ca3`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6c90ca31078d97124a3ad535ffe83da138558d67))
|
||||
|
||||
- **rpc_server**: Broadcasted data check
|
||||
([`c36852b`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/c36852b2ef762cdae3fde569bbd0d5f2f6f2725b))
|
||||
|
||||
- **rpc_server**: Enhance serialization logic for BECConnector objects and fix return types
|
||||
([`125afc8`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/125afc89073b4fc69a3f42650b3d4f6fa6ccaa47))
|
||||
|
||||
- **rpc_server**: Update _serialize_bec_connector to include wait parameter for registration check
|
||||
([`d6fccd1`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/d6fccd10f5d600ea67cf7b2a5ebb42295d15cdfe))
|
||||
|
||||
- **RPCReference**: Setattr added
|
||||
([`a2128ad`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a2128ad8d688995551c5e26974396fd0588b6804))
|
||||
|
||||
- **scan_control**: Restore scan parameters always regenerate the arg box, preventing infinite loop
|
||||
([`1f2db92`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1f2db927f50f4f30d43ebe52e39118c7d79994d4))
|
||||
|
||||
- **scan_matadata**: Parent passing
|
||||
([`4eaadd1`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4eaadd1545885b111fce3f8cab527a77b8633ff3))
|
||||
|
||||
- **scatter_waveform,waveform**: Added QTimer to fetch the last data points after 500ms
|
||||
([`e6795dd`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/e6795dd87ccd93cfd53e22cd94d71bffe1ef54dd))
|
||||
|
||||
- **serialization**: Add serialization for qpointf
|
||||
([`3ddfeaa`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3ddfeaa49fd4a7fdbff7cae47b90c25720f6dca0))
|
||||
|
||||
- **server**: Becdockarea type added
|
||||
([`4a74891`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4a74891184f112751258866b6bc9d800dbc5ed05))
|
||||
|
||||
- **server**: Remove window.hide() since widgets will be teared down on kill_server before siginit
|
||||
signals is sent
|
||||
([`58b0c7d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/58b0c7ddc1d0b85b35e7e18434c0b83aac01a735))
|
||||
|
||||
- **server**: Turn_off_the_lights cleanup fixed for parent_id widgets
|
||||
([`20a86ad`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/20a86ad325d36aa5aec73aeda7ff43ea9cc6c1f7))
|
||||
|
||||
- **setting_widget**: Added parent kwarg into all settings widgets in plotting framework
|
||||
([`94c2e2d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/94c2e2db6518402207b2a1077bb16403a8e61cee))
|
||||
|
||||
- **side_panel**: Side panel menu can be initialized without a title
|
||||
([`112eed6`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/112eed694c0ef6eb80ec7a7cfdfbaacf732d5b9f))
|
||||
|
||||
- **toolbar**: Update action check handling logic for SwitchableToolBarAction
|
||||
([`ac08bdf`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/ac08bdfab2162ac8fd103e60779a76d36e9a3765))
|
||||
|
||||
- **type hints**: Add future import to prevent sphinx from crashing
|
||||
([`aff5a51`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/aff5a51f4c059ce21ec72cefc263f37df2491480))
|
||||
|
||||
- **waveform**: Dap curve flickering
|
||||
([`b03d2ea`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b03d2eaeed4263846c470bc45eba9208ced2370b))
|
||||
|
||||
- **waveform**: Error where scan history is empty
|
||||
([`288ea4d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/288ea4dbbde6d5f770c37f4daf377da9ec8fe729))
|
||||
|
||||
- **waveform**: Fix dap curve categorization logic
|
||||
([`b91f1fe`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b91f1fe4879e43e71a1be49ce5a206efbae19315))
|
||||
|
||||
- **waveform**: Legend is correctly updated when changed from curve dialog
|
||||
([`c2d2c48`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/c2d2c484cd1f133f45fe7147616c22c0b5fd5611))
|
||||
|
||||
- **waveform**: Signals for x device can be defined from gui
|
||||
([`39164fe`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/39164feb18f9e996d97814f58157892e8db816ae))
|
||||
|
||||
- **waveform, rpc_reference**: __getitem__ removed form waveform and rpc_reference
|
||||
([`3a82c95`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3a82c95f60cb2b7f0b29a1ea5cdcbfa5bf602af8))
|
||||
|
||||
- **website-widget**: Add super().cleanup() in website widget
|
||||
([`8fbd54c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/8fbd54c3aa864623e42b39a1ebf92ba098ba437d))
|
||||
|
||||
- **widgets**: Becconnector resolves hierarchy including objectName, parent, parent_id upon init;
|
||||
all widgets adjusted
|
||||
([`a1bec75`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a1bec7511549277da231928d989b16ecad0eed1b))
|
||||
|
||||
### Build System
|
||||
|
||||
- Pyside6 capped to 6.9
|
||||
([`9dabf2c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/9dabf2c66c8023194964b9ad308e06197471f89f))
|
||||
|
||||
- **bec_lib**: Raised required version to 3.28.1
|
||||
([`a5f1f47`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a5f1f4781ed9787148053d71e0d12fefe42e142a))
|
||||
|
||||
- **dependencies**: Update min bec_lib version to 3.29
|
||||
([`eb0323b`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/eb0323b989e96e89d2eb1ff7b648edb43f5fe198))
|
||||
|
||||
### Continuous Integration
|
||||
|
||||
- **e2e**: E2e tests are saving logs
|
||||
([`d4106c5`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/d4106c548e2373463a48268fd991ded7f554e3a6))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add docs on widget plugins
|
||||
([`52a9f29`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/52a9f29bdcb20a9339a8970508bc0a93ba8bef5f))
|
||||
|
||||
- Add missing class doc strings for rpc-enabled widgets
|
||||
([`cfc8272`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/cfc8272ac288541d1e20c0840bd2ce6fa930897c))
|
||||
|
||||
- Better document logpanel code
|
||||
([`d2c9075`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/d2c90757c21e040940a378325cad75c4d94470f9))
|
||||
|
||||
- Grammar improvement
|
||||
([`1fe052e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1fe052e9da9e44bf9872db0d42218843a8e6d275))
|
||||
|
||||
- Remove BECFigure
|
||||
([`75cc45d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/75cc45d767970e985c566fd4aeccd4394f48dfa3))
|
||||
|
||||
- Remove BECFigure from docs, fix wrong api for docs of plotting widgets
|
||||
([`a1c859c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a1c859c7434357e6fb82f0912314c203fb73e890))
|
||||
|
||||
- Replaces instances of QtDesigner with BEC Designer for improved clarity
|
||||
([`60852e2`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/60852e228f80f0d2e74813f82bd30f1ba83ff154))
|
||||
|
||||
- Review quick_start
|
||||
([`4acf5be`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4acf5befb1dbcc69e8cc7da70ebf5663b9ec15f2))
|
||||
|
||||
- Update docs for v2
|
||||
([`25bd905`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/25bd905cef987a713e24aca178c04aef1ab59656))
|
||||
|
||||
- Update docs for various widgets
|
||||
([`b6695b4`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b6695b45d076dbf3896e94eedcf73d542022d764))
|
||||
|
||||
- Update quick_start
|
||||
([`afc818b`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/afc818bf7d17f42703c3cebafd2b292b8444647a))
|
||||
|
||||
- Updated docs for v2 ([#531](https://gitlab.psi.ch/bec/bec_widgets/-/merge_requests/531),
|
||||
[`b4af2cc`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b4af2cc77aa0013f4547cd98345b0c77abb7101b))
|
||||
|
||||
- **auto_updates**: Update documentation for auto updates functionality and add launcher image
|
||||
([`6630ba1`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6630ba1c421e566bf86ac38701a86eff624395d2))
|
||||
|
||||
- **lmfit**: Fix links
|
||||
([`5e4965f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/5e4965fe1f88d18fcc7e6875777ff3eb01ab08ec))
|
||||
|
||||
- **plot_base**: Update docstrings for properties and setters
|
||||
([`b085ef6`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b085ef6e730d529149bcb696b1ad4cd9c5220a83))
|
||||
|
||||
- **position-indicator**: Update docs for positioner indicator
|
||||
([`2f0d213`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/2f0d213e32fd662bfffd4df73b9281fa30cef6e3))
|
||||
|
||||
### Features
|
||||
|
||||
- Add loader/helper for widget plugins
|
||||
([`ca2bb4f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/ca2bb4f9b42ebaac2fc544d3da36267d93e9903d))
|
||||
|
||||
- Add rpc broadcast
|
||||
([`2ba9b4c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/2ba9b4cb236a2182261dfb88398d5ece733ba393))
|
||||
|
||||
- Add support for auto updates
|
||||
([`2511056`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/2511056557daf0b5dd78d3e85ac4befb8bf8c316))
|
||||
|
||||
- Delete bec_app
|
||||
([`8e64b65`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/8e64b65c2d3b8a8f3c6e5376e694369b41733da4))
|
||||
|
||||
- Deprecated and delete alignment_1d gui
|
||||
([`27ea92d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/27ea92d120cc8ef01fff10341ce0954b4f7fed5d))
|
||||
|
||||
- Namespace update for gui, dock_area and docks.
|
||||
([`ac3c5a3`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/ac3c5a38e449c2c3e4a1c61d5f9a59acfbf0cab5))
|
||||
|
||||
- **auto_update**: Add GUI highlight management for auto updates status
|
||||
([`5f272a6`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/5f272a66a4d4f65273d7b2a6709336cd3582d695))
|
||||
|
||||
- **auto_updates**: Enforce rpc widget class for subclasses of auto updates
|
||||
([`778230b`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/778230b5edf5d24df8a10c78c90ea065510e8344))
|
||||
|
||||
- **image**: New Image widget based on new PlotBase
|
||||
([`cb39ff3`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/cb39ff3fbde99f4e4bed49dee8a5e5987d257b23))
|
||||
|
||||
- **launch_window**: Add custom UI file launching functionality and UI tile
|
||||
([`3089ca1`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3089ca15ec4a8c110d11c57aff2da42f4af5bd08))
|
||||
|
||||
- **launch_window**: Add user access permissions
|
||||
([`8efa93d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/8efa93d2d2c5e6c28008e1bbde89e5cc8a01d139))
|
||||
|
||||
- **launch_window**: Enhance auto update functionality with selector and dynamic loading
|
||||
([`2965323`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/29653239c5cf43313224cc5123d066fcba4b831b))
|
||||
|
||||
- **launcher**: Add option for launching with auto updates
|
||||
([`20a1c5d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/20a1c5ddb3cd0763ce69bba5a893f54c56678706))
|
||||
|
||||
- **main_window**: Add launcher menu and functionality to show launcher
|
||||
([`55baa84`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/55baa84eb6723b30b407092bc36f826b826cc934))
|
||||
|
||||
- **motor_map**: New MotorMap widget based on PlotBase
|
||||
([`fec26d7`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/fec26d793e14965a719a4d038838418b9a7603bb))
|
||||
|
||||
- **multi_waveform**: Multi-waveform widget based on new PlotBase
|
||||
([`77f9616`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/77f96160ab348c1a65ceb55986ea4ea75f8be04a))
|
||||
|
||||
- **plugin_utils**: Add functionality to retrieve auto update classes from plugins
|
||||
([`c434af9`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/c434af9b92d68d08da87112ef424738e5e42ae6e))
|
||||
|
||||
- **positioner_box**: Add units QLabel to device UI components and update visibility logic
|
||||
([`f653fc5`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f653fc5f7ebf8ad5297facd739a8a49ea0a06c95))
|
||||
|
||||
- **scatter_waveform**: Scatter waveform widget based on new Plotbase
|
||||
([`95fcf01`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/95fcf016c32c52330acfd5900a3996c99c4ee01f))
|
||||
|
||||
- **server,launcher**: Rpc server separated with the launcher window introduced
|
||||
([`5f27a90`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/5f27a9098903ffd8ec27c1b45565f1c113892cca))
|
||||
|
||||
- **slot**: Add 'verify_sender' argument to SafeSlot for sender verification
|
||||
([`8eef425`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/8eef4253b0507f60f50c06ed48b59a1b19b29644))
|
||||
|
||||
- **ui_launch_window**: Add UILaunchWindow class
|
||||
([`45cd82e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/45cd82e6354c72e1e35cd6366aa7aad93f8b12ca))
|
||||
|
||||
- **waveform**: New Waveform widget based on NextGen PlotBase
|
||||
([`4bec181`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4bec181f3aff34d9de7d3f9ec012b641c125a661))
|
||||
|
||||
- **widget_io**: Added handler for Sliders
|
||||
([`1a0097e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1a0097e02728b6470217d3a574260f376776d81f))
|
||||
|
||||
### Refactoring
|
||||
|
||||
- Add fallback to 'index' plotting in case of missmatch in length
|
||||
([`515d7ad`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/515d7ad05584086a8e8ac626b476d629e27aacf3))
|
||||
|
||||
- Add pragma no cover to various TYPE_CHECKING
|
||||
([`f88dfc8`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f88dfc8f1bbc0819736a4f32bf21682366fd3437))
|
||||
|
||||
- Add support to plot against x_data
|
||||
([`0e276d4`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/0e276d4c09cddb459688aecf28684a963d8f6613))
|
||||
|
||||
- Add template for debugging the cli generator
|
||||
([`f89e74b`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f89e74b199d007cf47f355a1c5e1f582daeea90a))
|
||||
|
||||
- Autoupdate disabled
|
||||
([`4e29291`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4e29291b3a0891657a8d2011bcaf1d6e65de125a))
|
||||
|
||||
- Cleanup MR
|
||||
([`0b00cd2`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/0b00cd24fd43dbc87c81f7dbfae816343f7da4c4))
|
||||
|
||||
- Cleanup rpc reference tracking, fix appquit, fix namespace updates edge cases
|
||||
([`7ba93ce`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/7ba93ce934cc644ad6340f141a6a0888bd1d3d98))
|
||||
|
||||
- Cleanup, fix tests and _top_level dict/windows
|
||||
([`5872253`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/58722531232b2290f9fd974bae24877c9d5451f4))
|
||||
|
||||
- Fix cleanup bug for BECConnector items, renamed _registry_state to _server_registry
|
||||
([`be83c7d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/be83c7d5f4bc04d110734b491727dc60d8dd61ef))
|
||||
|
||||
- Fix cleanup for various widgets, including RoundedFrame
|
||||
([`d05179a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/d05179a519d6419c9631ffdf4fa6aa262966c2ed))
|
||||
|
||||
- Improve plotting behaviour from history
|
||||
([`ed2d958`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/ed2d958de62223cd796c869b6c8b9b75170e66f5))
|
||||
|
||||
- Rearrange base of metadata forms for generic use
|
||||
([`d04770f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/d04770fe913474ec9d4e06b056c85e720d1470c4))
|
||||
|
||||
- Set downsampling to auto=True, method 'peak', activate clipToView for (Async)-Curves and fix
|
||||
ViewAll hook from pg.view_box menu
|
||||
([`25820a1`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/25820a1cdec2cff99ab0d6085aece0e3e7dd9092))
|
||||
|
||||
- Tidy client generation and add options
|
||||
([`b492591`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b4925918f7acf31e40971814639be8a6c55d46df))
|
||||
|
||||
- **assets**: New icon for ui loader
|
||||
([`e5b5322`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/e5b532274ede281456a14b02a99855302603490a))
|
||||
|
||||
- **auto_update**: Auto_update changed to be BECMainWindow; removed auto update logic from
|
||||
BECDockArea
|
||||
([`56c2827`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/56c282714037f733bcfd8a659f34baadcd1aa223))
|
||||
|
||||
- **auto_updates**: Move cleanup method from user section to internal section
|
||||
([`ac9224e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/ac9224e5f2d3edcb5b1cc1cbc1a8583f81d0b912))
|
||||
|
||||
- **bec_connector**: Replace pyqtSlot with SafeSlot for consistency
|
||||
([`9d6d0b4`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/9d6d0b406a812e08ca8417415b5def98b40bdf92))
|
||||
|
||||
- **bec_figure**: Becfigure removed
|
||||
([`f76d931`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f76d9319bd13bb52b1ae2524c1c5e44a167cc330))
|
||||
|
||||
- **client_utils**: Remove unused auto update attributes from BECGuiClient
|
||||
([`b7795b4`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b7795b4d0ae21641bead0f1f1541f920ae95702a))
|
||||
|
||||
- **image_widget**: Old BECImageWidget removed
|
||||
([`de10609`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/de10609b3c714b80a14bf6940e86763d0779402b))
|
||||
|
||||
- **launch_window**: Remove cleanup method
|
||||
([`9a940bb`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/9a940bb8d58f37d1fc24ce4fdb38282d02349efb))
|
||||
|
||||
- **launcher,main_window**: Launcher window moved to inherit from BECMainWindow
|
||||
([`99383b7`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/99383b77150ca7c74c19c899a0e6a7879b770376))
|
||||
|
||||
- **motor_map_widget**: Becmotormapwidget removed
|
||||
([`f878e87`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f878e87ad545e0fe68292030d9f06dee693e0da2))
|
||||
|
||||
- **multi_waveform_widget**: Becmultiwaveformwidget removed
|
||||
([`7c31bbd`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/7c31bbd9c2e0230e54f4dca0f1e5c4d2cd6e7674))
|
||||
|
||||
- **plots**: Plot_next_gen module renamed to plots
|
||||
([`9fb9a1c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/9fb9a1cfd2a94efd5e2a9fcbaa05d65c7b7105ee))
|
||||
|
||||
- **plots**: Waveform and image rpc api review
|
||||
([`a3de1f0`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a3de1f0a31dfb9048493fb61983167960577fb97))
|
||||
|
||||
- **rpc_reference**: Refactor rpc reference tracking
|
||||
([`bd5e251`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/bd5e251ee9396633f419732e43411821726250aa))
|
||||
|
||||
- **rpc_server**: Add type hint for _get_becwidget_ancestor method parameter; minor cleanup of
|
||||
imports
|
||||
([`cb91ebc`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/cb91ebc0c34ae29b6b293996199f4624d36a3cc0))
|
||||
|
||||
- **rpc_server**: Add type hints and docstrings for heartbeat and registry update methods
|
||||
([`08168f2`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/08168f28d3c5375b9ace9df0d7aa31e33adb97e9))
|
||||
|
||||
- **rpc_server**: Cli_server renamed to rpc_server
|
||||
([`6082e7a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6082e7a6907c2fe15e4e5ebca857fbf8f222d192))
|
||||
|
||||
- **tests**: Create dummy scan item moved to client_mocks.py
|
||||
([`0dd9617`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/0dd9617e6e5ea756edc344c324451480a62bdae2))
|
||||
|
||||
- **ui_loader**: Remove unnecessary parent_id handling
|
||||
([`d60cf6c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/d60cf6c843ecc135a6065d1e913f9f6abb1a483d))
|
||||
|
||||
- **ui_loader**: Remove unused import
|
||||
([`a6ce312`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a6ce312f7c60c2babcc37127f7c69d54c1b32573))
|
||||
|
||||
- **utils**: Qt_utils moved to utils
|
||||
([`be552d3`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/be552d3ece97e7f472c4534b4af8438b95c518aa))
|
||||
|
||||
- **waveform_widget**: Removed and replaced by Waveform
|
||||
([`96cff49`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/96cff49cd4453fa70d8802653d5afe62d71c6b2a))
|
||||
|
||||
### Testing
|
||||
|
||||
- Add function scoped rpc_widgets e2e test; closes #510
|
||||
([`36dc174`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/36dc174bfedf212532658b84f8ab64971863d292))
|
||||
|
||||
- Add IPython client GUI object test module with tab completion
|
||||
([`e3d0d55`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/e3d0d5566c50f6a80ba861d4e3e0789f17785a46))
|
||||
|
||||
- Add tests for name creation of custom curves, and object name handling
|
||||
([`99d7623`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/99d76236cac63042f0d7d1db580dde8aa7cfd214))
|
||||
|
||||
- Disable test_bec_dock_rpc_e2e module, issue to fix this created #450
|
||||
([`17f2dda`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/17f2dda977025bc422e26289293d3fcbd224a6f6))
|
||||
|
||||
- Fix rpc widgets e2e test
|
||||
([`113938e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/113938e71a6dacba37164069e2c795cc9db168d4))
|
||||
|
||||
- Fix tests for launcher close / hide behavior
|
||||
([`23fee22`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/23fee22ef8f2c22f191dfc1da57b921484ede6cd))
|
||||
|
||||
- Fix tests for namespace updates
|
||||
([`f3d3c94`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f3d3c9425d3ed619b978427cea782137beedfb59))
|
||||
|
||||
- Qapp must shutdown cli server before checking for leaked QTimer
|
||||
([`d066051`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/d06605122e5c2e225650b44ebfc047daa5aa6f55))
|
||||
|
||||
- **bec_connector**: Becconnector requires a QObject
|
||||
([`23bdd95`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/23bdd95d8c6311d989cb3b807921e3fb2a3d62a0))
|
||||
|
||||
- **device_signal_input**: Fix init of device input widget
|
||||
([`31c3b64`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/31c3b64d7b157e5e26d44e5288afabef343c5e13))
|
||||
|
||||
- **e2e**: E2e tests adjusted for new plotting framework
|
||||
([`378398a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/378398a29b34e43f0cca0a49b08adfcb144e4777))
|
||||
|
||||
- **generate_cli**: Fix reference output
|
||||
([`a8adb06`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a8adb064f5011e1708ee2dc0090326f533407260))
|
||||
|
||||
- **launch_window**: Add test for launching UI file that raises ValueError for QMainWindow
|
||||
([`33a8a76`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/33a8a767f31a57bcfda624d503ed39e0e4578dcb))
|
||||
|
||||
- **launch_window**: Add unit tests for LaunchWindow initialization and custom UI file launching
|
||||
([`d5e422c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/d5e422c7fc0e169c35d7f206937e8c7902fbf123))
|
||||
|
||||
- **launch_window**: Tests for default and plugin auto updates
|
||||
([`e10f5ec`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/e10f5ec088c6937beb26ec468f510a209c7cc782))
|
||||
|
||||
- **plot_base**: Test for plot base re-enabled
|
||||
([`b51d637`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b51d637c5ff3418420801cd9b457fc073fa98adc))
|
||||
|
||||
- **plot_indicators**: Tests adapted to not be dependent on BECWaveformWidget
|
||||
([`360fe4c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/360fe4c9c3b5c3c1f26e97cb795aef8f4aba3b46))
|
||||
|
||||
- **setting_dialog**: Test that settings reject calls cleanup
|
||||
([`8914f1d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/8914f1d50600cab588a6cbecb08d85bfd1a715a1))
|
||||
|
||||
- **unit_tests**: Unit tests adjusted to use a modern plotting framework instead of BECFigure
|
||||
([`6ade934`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6ade93435632fa66fb012d92f9b8b548d96e718f))
|
||||
|
||||
|
||||
## v1.25.1 (2025-03-24)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -235,10 +235,8 @@ class LaunchWindow(BECMainWindow):
|
||||
raise ValueError(
|
||||
f"Name {name} must be unique for dock areas, but already exists: {existing_dock_areas}."
|
||||
)
|
||||
if not WidgetContainerUtils.has_name_valid_chars(name):
|
||||
raise ValueError(
|
||||
f"Name {name} contains invalid characters. Only alphanumeric characters, underscores, and dashes are allowed."
|
||||
)
|
||||
WidgetContainerUtils.raise_for_invalid_name(name)
|
||||
|
||||
else:
|
||||
name = "dock_area"
|
||||
name = WidgetContainerUtils.generate_unique_name(name, existing_dock_areas)
|
||||
@@ -284,6 +282,8 @@ class LaunchWindow(BECMainWindow):
|
||||
raise ValueError("UI file must be provided for custom UI file launch.")
|
||||
filename = os.path.basename(ui_file).split(".")[0]
|
||||
|
||||
WidgetContainerUtils.raise_for_invalid_name(filename)
|
||||
|
||||
tree = ET.parse(ui_file)
|
||||
root = tree.getroot()
|
||||
# Check if the top-level widget is a QMainWindow
|
||||
|
||||
@@ -7,6 +7,7 @@ import enum
|
||||
import inspect
|
||||
import traceback
|
||||
from functools import reduce
|
||||
from operator import add
|
||||
from typing import Literal, Optional
|
||||
|
||||
from bec_lib.logger import bec_logger
|
||||
@@ -469,6 +470,12 @@ class BECProgressBar(RPCBase):
|
||||
>>> progressbar.label_template = "$value / $percentage %"
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def _get_label(self) -> str:
|
||||
"""
|
||||
Return the label text. mostly used for testing rpc.
|
||||
"""
|
||||
|
||||
|
||||
class BECQueue(RPCBase):
|
||||
"""Widget to display the BEC queue."""
|
||||
@@ -483,6 +490,12 @@ class BECQueue(RPCBase):
|
||||
class BECStatusBox(RPCBase):
|
||||
"""An autonomous widget to display the status of BEC services."""
|
||||
|
||||
@rpc_call
|
||||
def get_server_state(self) -> "str":
|
||||
"""
|
||||
Get the state ("RUNNING", "BUSY", "IDLE", "ERROR") of the BEC server
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def remove(self):
|
||||
"""
|
||||
@@ -1317,14 +1330,14 @@ class ImageItem(RPCBase):
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def rotation(self) -> "Optional[int]":
|
||||
def num_rotation_90(self) -> "Optional[int]":
|
||||
"""
|
||||
Get or set the number of 90° rotations to apply.
|
||||
"""
|
||||
|
||||
@rotation.setter
|
||||
@num_rotation_90.setter
|
||||
@rpc_call
|
||||
def rotation(self) -> "Optional[int]":
|
||||
def num_rotation_90(self) -> "Optional[int]":
|
||||
"""
|
||||
Get or set the number of 90° rotations to apply.
|
||||
"""
|
||||
|
||||
@@ -41,6 +41,7 @@ class ClientGenerator:
|
||||
import inspect
|
||||
import traceback
|
||||
from functools import reduce
|
||||
from operator import add
|
||||
from typing import Literal, Optional
|
||||
"""
|
||||
if self._base
|
||||
@@ -222,18 +223,18 @@ class {class_name}(RPCBase):"""
|
||||
# Combine header and content, then format with black
|
||||
full_content = self.header + "\n" + self.content
|
||||
try:
|
||||
formatted_content = black.format_str(full_content, mode=black.FileMode(line_length=100))
|
||||
formatted_content = black.format_str(full_content, mode=black.Mode(line_length=100))
|
||||
except black.NothingChanged:
|
||||
formatted_content = full_content
|
||||
|
||||
isort.Config(
|
||||
config = isort.Config(
|
||||
profile="black",
|
||||
line_length=100,
|
||||
multi_line_output=3,
|
||||
include_trailing_comma=True,
|
||||
include_trailing_comma=False,
|
||||
known_first_party=["bec_widgets"],
|
||||
)
|
||||
formatted_content = isort.code(formatted_content)
|
||||
formatted_content = isort.code(formatted_content, config=config)
|
||||
|
||||
with open(file_name, "w", encoding="utf-8") as file:
|
||||
file.write(formatted_content)
|
||||
@@ -318,5 +319,5 @@ def main():
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
import sys
|
||||
|
||||
sys.argv = ["bw-generate-cli", "--target", "csaxs_bec"]
|
||||
sys.argv = ["bw-generate-cli", "--target", "bec_widgets"]
|
||||
main()
|
||||
|
||||
@@ -16,9 +16,9 @@ if PYSIDE6:
|
||||
from PySide6.scripts.pyside_tool import (
|
||||
_extend_path_var,
|
||||
init_virtual_env,
|
||||
qt_tool_wrapper,
|
||||
is_pyenv_python,
|
||||
is_virtual_env,
|
||||
qt_tool_wrapper,
|
||||
ui_tool_binary,
|
||||
)
|
||||
|
||||
@@ -78,7 +78,7 @@ def list_editable_packages() -> set[str]:
|
||||
return editable_packages
|
||||
|
||||
|
||||
def patch_designer(): # pragma: no cover
|
||||
def patch_designer(cmd_args: list[str] = []): # pragma: no cover
|
||||
if not PYSIDE6:
|
||||
print("PYSIDE6 is not available in the environment. Cannot patch designer.")
|
||||
return
|
||||
@@ -119,7 +119,7 @@ def patch_designer(): # pragma: no cover
|
||||
editable_packages = list_editable_packages()
|
||||
for pckg in editable_packages:
|
||||
_extend_path_var("PYTHONPATH", pckg, True)
|
||||
qt_tool_wrapper(ui_tool_binary("designer"), sys.argv[1:])
|
||||
qt_tool_wrapper(ui_tool_binary("designer"), cmd_args)
|
||||
|
||||
|
||||
def find_plugin_paths(base_path: Path):
|
||||
@@ -147,7 +147,7 @@ def set_plugin_environment_variable(plugin_paths):
|
||||
|
||||
|
||||
# Patch the designer function
|
||||
def main(): # pragma: no cover
|
||||
def open_designer(cmd_args: list[str] = []): # pragma: no cover
|
||||
if not PYSIDE6:
|
||||
print("PYSIDE6 is not available in the environment. Exiting...")
|
||||
return
|
||||
@@ -160,7 +160,11 @@ def main(): # pragma: no cover
|
||||
|
||||
set_plugin_environment_variable(plugin_paths)
|
||||
|
||||
patch_designer()
|
||||
patch_designer(cmd_args)
|
||||
|
||||
|
||||
def main():
|
||||
open_designer(sys.argv[1:])
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
|
||||
@@ -25,10 +25,20 @@ if TYPE_CHECKING: # pragma: no cover
|
||||
|
||||
|
||||
class QtThreadSafeCallback(QObject):
|
||||
"""QtThreadSafeCallback is a wrapper around a callback function to make it thread-safe for Qt."""
|
||||
|
||||
cb_signal = pyqtSignal(dict, dict)
|
||||
|
||||
def __init__(self, cb):
|
||||
def __init__(self, cb: Callable, cb_info: dict | None = None):
|
||||
"""
|
||||
Initialize the QtThreadSafeCallback.
|
||||
|
||||
Args:
|
||||
cb (Callable): The callback function to be wrapped.
|
||||
cb_info (dict, optional): Additional information about the callback. Defaults to None.
|
||||
"""
|
||||
super().__init__()
|
||||
self.cb_info = cb_info
|
||||
|
||||
self.cb = cb
|
||||
self.cb_signal.connect(self.cb)
|
||||
@@ -37,7 +47,7 @@ class QtThreadSafeCallback(QObject):
|
||||
# make 2 differents QtThreadSafeCallback to look
|
||||
# identical when used as dictionary keys, if the
|
||||
# callback is the same
|
||||
return id(self.cb)
|
||||
return f"{id(self.cb)}{self.cb_info}".__hash__()
|
||||
|
||||
def __call__(self, msg_content, metadata):
|
||||
self.cb_signal.emit(msg_content, metadata)
|
||||
@@ -141,6 +151,7 @@ class BECDispatcher:
|
||||
self,
|
||||
slot: Callable,
|
||||
topics: Union[EndpointInfo, str, list[Union[EndpointInfo, str]]],
|
||||
cb_info: dict | None = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Connect widget's qt slot, so that it is called on new pub/sub topic message.
|
||||
@@ -149,8 +160,9 @@ class BECDispatcher:
|
||||
slot (Callable): A slot method/function that accepts two inputs: content and metadata of
|
||||
the corresponding pub/sub message
|
||||
topics (EndpointInfo | str | list): A topic or list of topics that can typically be acquired via bec_lib.MessageEndpoints
|
||||
cb_info (dict | None): A dictionary containing information about the callback. Defaults to None.
|
||||
"""
|
||||
slot = QtThreadSafeCallback(slot)
|
||||
slot = QtThreadSafeCallback(cb=slot, cb_info=cb_info)
|
||||
self.client.connector.register(topics, cb=slot, **kwargs)
|
||||
topics_str, _ = self.client.connector._convert_endpointinfo(topics)
|
||||
self._slots[slot].update(set(topics_str))
|
||||
|
||||
@@ -21,7 +21,7 @@ def _submodule_specs(module: ModuleType) -> tuple[ModuleSpec | None, ...]:
|
||||
|
||||
|
||||
def _loaded_submodules_from_specs(
|
||||
submodule_specs: tuple[ModuleSpec | None, ...]
|
||||
submodule_specs: tuple[ModuleSpec | None, ...],
|
||||
) -> Generator[ModuleType, None, None]:
|
||||
"""Load all submodules from the given specs."""
|
||||
for submodule in (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
""" This custom class is a thin wrapper around the SignalProxy class to allow signal calls to be blocked.
|
||||
"""This custom class is a thin wrapper around the SignalProxy class to allow signal calls to be blocked.
|
||||
Unblocking the proxy needs to be done through the slot unblock_proxy. The most likely use case for this class is
|
||||
when the callback function is potentially initiating a slower progress, i.e. requesting a data analysis routine to
|
||||
when the callback function is potentially initiating a slower progress, i.e. requesting a data analysis routine to
|
||||
analyse data. Requesting a new fit may lead to request piling up and an overall slow done of performance. This proxy
|
||||
will allow you to decide by yourself when to unblock and execute the callback again."""
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
from typing import Literal, Type
|
||||
from typing import Any, Type
|
||||
|
||||
from qtpy.QtWidgets import QWidget
|
||||
|
||||
from bec_widgets.cli.rpc.rpc_register import RPCRegister
|
||||
from bec_widgets.cli.client_utils import BECGuiClient
|
||||
|
||||
|
||||
class WidgetContainerUtils:
|
||||
@@ -73,3 +72,36 @@ class WidgetContainerUtils:
|
||||
return None
|
||||
else:
|
||||
raise ValueError(f"No widget of class {widget_class} found.")
|
||||
|
||||
@staticmethod
|
||||
def name_is_protected(name: str, container: Any = None) -> bool:
|
||||
"""
|
||||
Check if the name is not protected.
|
||||
|
||||
Args:
|
||||
name(str): The name to be checked.
|
||||
|
||||
Returns:
|
||||
bool: True if the name is not protected, False otherwise.
|
||||
"""
|
||||
if container is None:
|
||||
container = BECGuiClient
|
||||
gui_client_methods = set(filter(lambda x: not x.startswith("_"), dir(container)))
|
||||
return name in gui_client_methods
|
||||
|
||||
@staticmethod
|
||||
def raise_for_invalid_name(name: str, container: Any = None) -> None:
|
||||
"""
|
||||
Check if the name is valid. If not, raise a ValueError.
|
||||
|
||||
Args:
|
||||
name(str): The name to be checked.
|
||||
Raises:
|
||||
ValueError: If the name is not valid.
|
||||
"""
|
||||
if not WidgetContainerUtils.has_name_valid_chars(name):
|
||||
raise ValueError(
|
||||
f"Name '{name}' contains invalid characters. Only alphanumeric characters, underscores, and dashes are allowed."
|
||||
)
|
||||
if WidgetContainerUtils.name_is_protected(name, container):
|
||||
raise ValueError(f"Name '{name}' is protected. Please choose another name.")
|
||||
|
||||
@@ -99,16 +99,30 @@ def SafeSlot(*slot_args, **slot_kwargs): # pylint: disable=invalid-name
|
||||
'verify_sender' keyword argument can be passed with boolean value if the sender should be verified
|
||||
before executing the slot. If True, the slot will only execute if the sender is a QObject. This is
|
||||
useful to prevent function calls from already deleted objects.
|
||||
'raise_error' keyword argument can be passed with boolean value if the error should be raised
|
||||
after the error is displayed. This is useful to propagate the error to the caller but should be used
|
||||
with great care to avoid segfaults.
|
||||
|
||||
The keywords above are stored in a container which can be overridden by passing
|
||||
'_override_slot_params' keyword argument with a dictionary containing the keywords to override.
|
||||
This is useful to override the default behavior of the decorator for a specific function call.
|
||||
|
||||
"""
|
||||
popup_error = bool(slot_kwargs.pop("popup_error", False))
|
||||
verify_sender = bool(slot_kwargs.pop("verify_sender", False))
|
||||
_slot_params = {
|
||||
"popup_error": bool(slot_kwargs.pop("popup_error", False)),
|
||||
"verify_sender": bool(slot_kwargs.pop("verify_sender", False)),
|
||||
"raise_error": bool(slot_kwargs.pop("raise_error", False)),
|
||||
}
|
||||
|
||||
def error_managed(method):
|
||||
@Slot(*slot_args, **slot_kwargs)
|
||||
@functools.wraps(method)
|
||||
def wrapper(*args, **kwargs):
|
||||
|
||||
_override_slot_params = kwargs.pop("_override_slot_params", {})
|
||||
_slot_params.update(_override_slot_params)
|
||||
try:
|
||||
if not verify_sender or len(args) == 0:
|
||||
if not _slot_params["verify_sender"] or len(args) == 0:
|
||||
return method(*args, **kwargs)
|
||||
|
||||
_instance = args[0]
|
||||
@@ -126,11 +140,11 @@ def SafeSlot(*slot_args, **slot_kwargs): # pylint: disable=invalid-name
|
||||
except Exception:
|
||||
slot_name = f"{method.__module__}.{method.__qualname__}"
|
||||
error_msg = traceback.format_exc()
|
||||
if popup_error:
|
||||
ErrorPopupUtility().custom_exception_hook(
|
||||
*sys.exc_info(), popup_error=popup_error
|
||||
)
|
||||
if _slot_params["popup_error"]:
|
||||
ErrorPopupUtility().custom_exception_hook(*sys.exc_info(), popup_error=True)
|
||||
logger.error(f"SafeSlot error in slot '{slot_name}':\n{error_msg}")
|
||||
if _slot_params["raise_error"]:
|
||||
raise
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
""" Module for a thin wrapper (LinearRegionWrapper) around the LinearRegionItem in pyqtgraph.
|
||||
The class is mainly designed for usage with the BECWaveform and 1D plots. """
|
||||
"""Module for a thin wrapper (LinearRegionWrapper) around the LinearRegionItem in pyqtgraph.
|
||||
The class is mainly designed for usage with the BECWaveform and 1D plots."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
@@ -303,11 +303,7 @@ class BECDock(BECWidget, Dock):
|
||||
shift(Literal["down", "up", "left", "right"]): The direction to shift the widgets if the position is occupied.
|
||||
"""
|
||||
if name is not None:
|
||||
if not WidgetContainerUtils.has_name_valid_chars(name):
|
||||
raise ValueError(
|
||||
f"Name {name} contains invalid characters. "
|
||||
f"Only alphanumeric characters and underscores are allowed."
|
||||
)
|
||||
WidgetContainerUtils.raise_for_invalid_name(name, container=self)
|
||||
|
||||
if row is None:
|
||||
row = self.layout.rowCount()
|
||||
|
||||
@@ -308,6 +308,8 @@ class BECDockArea(BECWidget, QWidget):
|
||||
"""
|
||||
if state is None:
|
||||
state = self.config.docks_state
|
||||
if state is None:
|
||||
return
|
||||
self.dock_area.restoreState(state, missing=missing, extra=extra)
|
||||
|
||||
@SafeSlot()
|
||||
@@ -364,11 +366,8 @@ class BECDockArea(BECWidget, QWidget):
|
||||
f"Name {name} must be unique for docks, but already exists in DockArea "
|
||||
f"with name: {self.object_name} and id {self.gui_id}."
|
||||
)
|
||||
if not WidgetContainerUtils.has_name_valid_chars(name):
|
||||
raise ValueError(
|
||||
f"Name {name} contains invalid characters. "
|
||||
f"Only alphanumeric characters and underscores are allowed."
|
||||
)
|
||||
WidgetContainerUtils.raise_for_invalid_name(name, container=self)
|
||||
|
||||
else: # Name is not provided
|
||||
name = WidgetContainerUtils.generate_unique_name(name="dock", list_of_names=dock_names)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
""" Module for a PositionerGroup widget to control a positioner device."""
|
||||
"""Module for a PositionerGroup widget to control a positioner device."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
""" Module for DapComboBox widget class to select a DAP model from a combobox. """
|
||||
"""Module for DapComboBox widget class to select a DAP model from a combobox."""
|
||||
|
||||
from bec_lib.logger import bec_logger
|
||||
from qtpy.QtCore import Property, Signal, Slot
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
BECConsole is a Qt widget that runs a Bash shell.
|
||||
BECConsole is a Qt widget that runs a Bash shell.
|
||||
|
||||
BECConsole VT100 emulation is powered by Pyte,
|
||||
(https://github.com/selectel/pyte).
|
||||
@@ -56,12 +56,12 @@ control_keys_mapping = {
|
||||
QtCore.Qt.Key_G: b"\x07", # Ctrl-G (Bell)
|
||||
QtCore.Qt.Key_H: b"\x08", # Ctrl-H (Backspace)
|
||||
QtCore.Qt.Key_I: b"\x09", # Ctrl-I (Tab)
|
||||
QtCore.Qt.Key_J: b"\x0A", # Ctrl-J (Line Feed)
|
||||
QtCore.Qt.Key_K: b"\x0B", # Ctrl-K (Vertical Tab)
|
||||
QtCore.Qt.Key_L: b"\x0C", # Ctrl-L (Form Feed)
|
||||
QtCore.Qt.Key_M: b"\x0D", # Ctrl-M (Carriage Return)
|
||||
QtCore.Qt.Key_N: b"\x0E", # Ctrl-N
|
||||
QtCore.Qt.Key_O: b"\x0F", # Ctrl-O
|
||||
QtCore.Qt.Key_J: b"\x0a", # Ctrl-J (Line Feed)
|
||||
QtCore.Qt.Key_K: b"\x0b", # Ctrl-K (Vertical Tab)
|
||||
QtCore.Qt.Key_L: b"\x0c", # Ctrl-L (Form Feed)
|
||||
QtCore.Qt.Key_M: b"\x0d", # Ctrl-M (Carriage Return)
|
||||
QtCore.Qt.Key_N: b"\x0e", # Ctrl-N
|
||||
QtCore.Qt.Key_O: b"\x0f", # Ctrl-O
|
||||
QtCore.Qt.Key_P: b"\x10", # Ctrl-P
|
||||
QtCore.Qt.Key_Q: b"\x11", # Ctrl-Q
|
||||
QtCore.Qt.Key_R: b"\x12", # Ctrl-R
|
||||
@@ -72,10 +72,10 @@ control_keys_mapping = {
|
||||
QtCore.Qt.Key_W: b"\x17", # Ctrl-W
|
||||
QtCore.Qt.Key_X: b"\x18", # Ctrl-X
|
||||
QtCore.Qt.Key_Y: b"\x19", # Ctrl-Y
|
||||
QtCore.Qt.Key_Z: b"\x1A", # Ctrl-Z
|
||||
QtCore.Qt.Key_Escape: b"\x1B", # Ctrl-Escape
|
||||
QtCore.Qt.Key_Backslash: b"\x1C", # Ctrl-\
|
||||
QtCore.Qt.Key_Underscore: b"\x1F", # Ctrl-_
|
||||
QtCore.Qt.Key_Z: b"\x1a", # Ctrl-Z
|
||||
QtCore.Qt.Key_Escape: b"\x1b", # Ctrl-Escape
|
||||
QtCore.Qt.Key_Backslash: b"\x1c", # Ctrl-\
|
||||
QtCore.Qt.Key_Underscore: b"\x1f", # Ctrl-_
|
||||
}
|
||||
|
||||
normal_keys_mapping = {
|
||||
@@ -89,7 +89,7 @@ normal_keys_mapping = {
|
||||
QtCore.Qt.Key_Left: b"\x02",
|
||||
QtCore.Qt.Key_Up: b"\x10",
|
||||
QtCore.Qt.Key_Right: b"\x06",
|
||||
QtCore.Qt.Key_Down: b"\x0E",
|
||||
QtCore.Qt.Key_Down: b"\x0e",
|
||||
QtCore.Qt.Key_PageUp: b"\x49",
|
||||
QtCore.Qt.Key_PageDown: b"\x51",
|
||||
QtCore.Qt.Key_F1: b"\x1b\x31",
|
||||
|
||||
@@ -61,8 +61,8 @@ class ImageItem(BECConnector, pg.ImageItem):
|
||||
"fft.setter",
|
||||
"log",
|
||||
"log.setter",
|
||||
"rotation",
|
||||
"rotation.setter",
|
||||
"num_rotation_90",
|
||||
"num_rotation_90.setter",
|
||||
"transpose",
|
||||
"transpose.setter",
|
||||
"get_data",
|
||||
|
||||
@@ -98,6 +98,7 @@ class PlotBase(BECWidget, QWidget):
|
||||
self._ui_mode = UIMode.POPUP if popups else UIMode.SIDE
|
||||
self.axis_settings_dialog = None
|
||||
self.plot_widget = pg.GraphicsLayoutWidget(parent=self)
|
||||
self.plot_widget.ci.setContentsMargins(0, 0, 0, 0)
|
||||
self.plot_item = pg.PlotItem(viewBox=BECViewBox(enableMenu=True))
|
||||
self.plot_widget.addItem(self.plot_item)
|
||||
self.side_panel = SidePanel(self, orientation="left", panel_max_width=280)
|
||||
@@ -795,6 +796,7 @@ class PlotBase(BECWidget, QWidget):
|
||||
"""
|
||||
self.plot_item.showAxis("top", value)
|
||||
self.plot_item.showAxis("right", value)
|
||||
|
||||
self.property_changed.emit("outer_axes", value)
|
||||
|
||||
@SafeProperty(bool, doc="Show inner axes of the plot widget.")
|
||||
@@ -814,6 +816,7 @@ class PlotBase(BECWidget, QWidget):
|
||||
"""
|
||||
self.plot_item.showAxis("bottom", value)
|
||||
self.plot_item.showAxis("left", value)
|
||||
|
||||
self._apply_x_label()
|
||||
self._apply_y_label()
|
||||
self.property_changed.emit("inner_axes", value)
|
||||
|
||||
@@ -137,6 +137,7 @@ class Waveform(PlotBase):
|
||||
# Curve data
|
||||
self._sync_curves = []
|
||||
self._async_curves = []
|
||||
self._async_connected_devices: set[str] = set()
|
||||
self._slice_index = None
|
||||
self._dap_curves = []
|
||||
self._mode: Literal["none", "sync", "async", "mixed"] = "none"
|
||||
@@ -544,6 +545,7 @@ class Waveform(PlotBase):
|
||||
continue
|
||||
config = CurveConfig(**cfg_dict)
|
||||
self._add_curve(config=config)
|
||||
self.update_with_scan_history(-1)
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"Failed to decode JSON: {e}")
|
||||
|
||||
@@ -1012,6 +1014,7 @@ class Waveform(PlotBase):
|
||||
return
|
||||
|
||||
if current_scan_id != self.scan_id:
|
||||
self._async_connected_devices.clear()
|
||||
self.reset()
|
||||
self.new_scan.emit()
|
||||
self.new_scan_id.emit(current_scan_id)
|
||||
@@ -1178,17 +1181,22 @@ class Waveform(PlotBase):
|
||||
except KeyError:
|
||||
logger.warning(f"Curve {name} not found in plot item.")
|
||||
pass
|
||||
self.bec_dispatcher.connect_slot(
|
||||
self.on_async_readback,
|
||||
MessageEndpoints.device_async_readback(self.scan_id, name),
|
||||
from_start=True,
|
||||
)
|
||||
logger.info(f"Setup async curve {name}")
|
||||
|
||||
@SafeSlot(dict, dict)
|
||||
# Connect only once per device signal
|
||||
if name not in self._async_connected_devices:
|
||||
self.bec_dispatcher.connect_slot(
|
||||
self.on_async_readback,
|
||||
MessageEndpoints.device_async_readback(self.scan_id, name),
|
||||
from_start=True,
|
||||
cb_info={"scan_id": self.scan_id},
|
||||
)
|
||||
self._async_connected_devices.add(name)
|
||||
logger.info(f"Async read-back connected for {name}")
|
||||
|
||||
@SafeSlot(dict, dict, verify_sender=True)
|
||||
def on_async_readback(self, msg, metadata):
|
||||
"""
|
||||
Get async data readback. This code needs to be fast, therefor we try
|
||||
Get async data readback. This code needs to be fast; therefore, we try
|
||||
to reduce the number of copies in between cycles. Be careful when refactoring
|
||||
this part as it will affect the performance of the async readback.
|
||||
|
||||
@@ -1204,6 +1212,14 @@ class Waveform(PlotBase):
|
||||
msg(dict): Message with the async data.
|
||||
metadata(dict): Metadata of the message.
|
||||
"""
|
||||
sender = self.sender()
|
||||
if not hasattr(sender, "cb_info"):
|
||||
logger.info(f"Sender {sender} has no cb_info.")
|
||||
return
|
||||
scan_id = sender.cb_info.get("scan_id", None)
|
||||
if scan_id != self.scan_id:
|
||||
logger.info("Scan ID mismatch, ignoring async readback.")
|
||||
|
||||
instruction = metadata.get("async_update", {}).get("type")
|
||||
if instruction not in ["add", "add_slice", "replace"]:
|
||||
logger.warning(f"Invalid async update instruction: {instruction}")
|
||||
@@ -1212,6 +1228,7 @@ class Waveform(PlotBase):
|
||||
plot_mode = self.x_axis_mode["name"]
|
||||
for curve in self._async_curves:
|
||||
x_data = None # Reset x_data
|
||||
y_data = None # Reset y_data
|
||||
# Get the curve data
|
||||
async_data = msg["signals"].get(curve.config.signal.entry, None)
|
||||
if async_data is None:
|
||||
|
||||
@@ -21,6 +21,7 @@ class BECProgressBar(BECWidget, QWidget):
|
||||
"set_minimum",
|
||||
"label_template",
|
||||
"label_template.setter",
|
||||
"_get_label",
|
||||
]
|
||||
ICON_NAME = "page_control"
|
||||
|
||||
@@ -235,6 +236,10 @@ class BECProgressBar(BECWidget, QWidget):
|
||||
(value - self._user_minimum) / (self._user_maximum - self._user_minimum) * self._maximum
|
||||
)
|
||||
|
||||
def _get_label(self) -> str:
|
||||
"""Return the label text. mostly used for testing rpc."""
|
||||
return self.center_label.text()
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
@@ -76,6 +76,7 @@ class BECStatusBox(BECWidget, CompactPopupWidget):
|
||||
|
||||
PLUGIN = True
|
||||
CORE_SERVICES = ["DeviceServer", "ScanServer", "SciHub", "ScanBundler", "FileWriterManager"]
|
||||
USER_ACCESS = ["get_server_state", "remove"]
|
||||
|
||||
service_update = Signal(BECServiceInfoContainer)
|
||||
bec_core_state = Signal(str)
|
||||
@@ -134,6 +135,10 @@ class BECStatusBox(BECWidget, CompactPopupWidget):
|
||||
"QTreeWidget::item:selected {}"
|
||||
)
|
||||
|
||||
def get_server_state(self) -> str:
|
||||
"""Get the state ("RUNNING", "BUSY", "IDLE", "ERROR") of the BEC server"""
|
||||
return self.status_container[self.box_name]["info"].status
|
||||
|
||||
def _create_status_widget(
|
||||
self, service_name: str, status=BECStatus, info: dict = None, metrics: dict = None
|
||||
) -> StatusItem:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
""" Module for a StatusItem widget to display status and metrics for a BEC service.
|
||||
"""Module for a StatusItem widget to display status and metrics for a BEC service.
|
||||
The widget is bound to be used with the BECStatusBox widget."""
|
||||
|
||||
import enum
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
""" Utilities for filtering and formatting in the LogPanel"""
|
||||
"""Utilities for filtering and formatting in the LogPanel"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from pyqtgraph.widgets.ColorMapButton import ColorMapButton
|
||||
from qtpy import QtCore, QtGui
|
||||
from qtpy.QtCore import Property, Signal, Slot
|
||||
from qtpy.QtWidgets import QSizePolicy, QVBoxLayout, QWidget
|
||||
|
||||
@@ -6,6 +7,23 @@ from bec_widgets.utils import Colors
|
||||
from bec_widgets.utils.bec_widget import BECWidget
|
||||
|
||||
|
||||
class RoundedColorMapButton(ColorMapButton):
|
||||
"""Thin wrapper around pyqtgraph ColorMapButton to add rounded clipping."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||
|
||||
def paintEvent(self, evt):
|
||||
painter = QtGui.QPainter(self)
|
||||
painter.setRenderHint(QtGui.QPainter.Antialiasing)
|
||||
path = QtGui.QPainterPath()
|
||||
path.addRoundedRect(self.rect(), 8, 8)
|
||||
painter.setClipPath(path)
|
||||
self.paintColorMap(painter, self.contentsRect())
|
||||
painter.end()
|
||||
|
||||
|
||||
class BECColorMapWidget(BECWidget, QWidget):
|
||||
colormap_changed_signal = Signal(str)
|
||||
ICON_NAME = "palette"
|
||||
@@ -15,7 +33,7 @@ class BECColorMapWidget(BECWidget, QWidget):
|
||||
def __init__(self, parent=None, cmap: str = "plasma", **kwargs):
|
||||
super().__init__(parent=parent, **kwargs)
|
||||
# Create the ColorMapButton
|
||||
self.button = ColorMapButton()
|
||||
self.button = RoundedColorMapButton()
|
||||
|
||||
# Set the size policy and minimum width
|
||||
size_policy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "bec_widgets"
|
||||
version = "1.25.1"
|
||||
version = "2.1.2"
|
||||
description = "BEC Widgets"
|
||||
requires-python = ">=3.10"
|
||||
classifiers = [
|
||||
@@ -16,7 +16,7 @@ dependencies = [
|
||||
"bec_ipython_client>=2.21.4, <=4.0", # needed for jupyter console
|
||||
"bec_lib>=3.29, <=4.0",
|
||||
"bec_qthemes~=0.7, >=0.7",
|
||||
"black~=24.0", # needed for bw-generate-cli
|
||||
"black~=25.0", # needed for bw-generate-cli
|
||||
"isort~=5.13, >=5.13.2", # needed for bw-generate-cli
|
||||
"pydantic~=2.0",
|
||||
"pyqtgraph~=0.13",
|
||||
@@ -31,6 +31,7 @@ dependencies = [
|
||||
dev = [
|
||||
"coverage~=7.0",
|
||||
"fakeredis~=2.23, >=2.23.2",
|
||||
"isort~=5.13, >=5.13.2",
|
||||
"pytest-bec-e2e>=2.21.4, <=4.0",
|
||||
"pytest-qt~=4.4",
|
||||
"pytest-random-order~=1.1",
|
||||
|
||||
@@ -4,8 +4,7 @@ import random
|
||||
|
||||
import pytest
|
||||
|
||||
from bec_widgets.cli.client_utils import BECGuiClient, _start_plot_process
|
||||
from bec_widgets.utils import BECDispatcher
|
||||
from bec_widgets.cli.client_utils import BECGuiClient
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
# pylint: disable=redefined-outer-name
|
||||
@@ -28,7 +27,7 @@ def gui_id():
|
||||
return f"figure_{random.randint(0,100)}" # make a new gui id each time, to ensure no 'gui is alive' zombie key can perturbate
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@pytest.fixture(scope="function")
|
||||
def connected_client_gui_obj(qtbot, gui_id, bec_client_lib):
|
||||
"""
|
||||
Fixture to create a new BECGuiClient object and start a server in the background.
|
||||
@@ -42,22 +41,3 @@ def connected_client_gui_obj(qtbot, gui_id, bec_client_lib):
|
||||
yield gui
|
||||
finally:
|
||||
gui.kill_server()
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def connected_gui_with_scope_session(qtbot, gui_id, bec_client_lib):
|
||||
"""
|
||||
Fixture to create a new BECGuiClient object and start a server in the background.
|
||||
|
||||
This fixture is scoped to the session, meaning it remains alive for all tests in the session.
|
||||
We can use this fixture to create a gui object that is used across multiple tests, and
|
||||
simulate a real-world scenario where the gui is not restarted for each test.
|
||||
"""
|
||||
gui = BECGuiClient(gui_id=gui_id)
|
||||
try:
|
||||
gui.start(wait=True)
|
||||
# After the server started, we need to wait until the bec exists in the namespace
|
||||
qtbot.waitUntil(lambda: hasattr(gui, "bec"), timeout=5000)
|
||||
yield gui
|
||||
finally:
|
||||
gui.kill_server()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Test module for the gui object within the BEC IPython client.
|
||||
Test module for the gui object within the BEC IPython client.
|
||||
"""
|
||||
|
||||
from unittest import mock
|
||||
|
||||
@@ -136,7 +136,7 @@ def test_async_plotting(qtbot, bec_client_lib, connected_client_gui_obj):
|
||||
dev.waveform.sim.select_model("GaussianModel")
|
||||
dev.waveform.sim.params = {"amplitude": 1000, "center": 4000, "sigma": 300}
|
||||
dev.waveform.async_update.set("add").wait()
|
||||
dev.waveform.waveform_shape.set(1000).wait()
|
||||
dev.waveform.waveform_shape.set(10000).wait()
|
||||
wf = dock.new("wf_dock").new("Waveform")
|
||||
curve = wf.plot(y_name="waveform")
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from bec_widgets.cli.rpc.rpc_base import RPCReference
|
||||
|
||||
def test_rpc_reference_objects(connected_client_gui_obj):
|
||||
gui = connected_client_gui_obj
|
||||
dock = gui.window_list[0].new("dock")
|
||||
dock = gui.window_list[0].new()
|
||||
plt = dock.new(name="fig", widget="Waveform")
|
||||
|
||||
plt.plot(x_name="samx", y_name="bpm4i")
|
||||
|
||||
0
tests/end-2-end/user_interaction/__init__.py
Normal file
0
tests/end-2-end/user_interaction/__init__.py
Normal file
82
tests/end-2-end/user_interaction/conftest.py
Normal file
82
tests/end-2-end/user_interaction/conftest.py
Normal file
@@ -0,0 +1,82 @@
|
||||
"""
|
||||
End-2-End test fixtures for module scoped testing. The fixtures overwrite the default versions used
|
||||
for the function scoped tests. The fixtures will only be created once for this entire module, meaning
|
||||
that any test can be used to test user interaction and potential leakage of threads or other resources across
|
||||
different widgets.
|
||||
"""
|
||||
|
||||
import random
|
||||
|
||||
import pytest
|
||||
from bec_ipython_client import BECIPythonClient
|
||||
from bec_lib.redis_connector import RedisConnector
|
||||
from bec_lib.service_config import ServiceConfig
|
||||
from bec_lib.tests.utils import wait_for_empty_queue
|
||||
from pytestqt.plugin import QtBot
|
||||
|
||||
from bec_widgets.cli.client_utils import BECGuiClient
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
# pylint: disable=redefined-outer-name
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def gui_id():
|
||||
"""New gui id each time, to ensure no 'gui is alive' zombie key can perturbate"""
|
||||
return f"figure_{random.randint(0,100)}"
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def bec_ipython_client_with_demo_config(
|
||||
bec_redis_fixture, bec_services_config_file_path, bec_servers
|
||||
):
|
||||
"""Fixture to create a BECIPythonClient with a demo config."""
|
||||
config = ServiceConfig(bec_services_config_file_path)
|
||||
bec = BECIPythonClient(config, RedisConnector, forced=True)
|
||||
bec.start()
|
||||
bec.config.load_demo_config()
|
||||
try:
|
||||
yield bec
|
||||
finally:
|
||||
bec.shutdown()
|
||||
bec._client._reset_singleton()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def bec_client_lib(bec_ipython_client_with_demo_config):
|
||||
"""Fixture to create a BECIPythonClient with a demo config."""
|
||||
bec = bec_ipython_client_with_demo_config
|
||||
bec.queue.request_queue_reset()
|
||||
bec.queue.request_scan_continuation()
|
||||
wait_for_empty_queue(bec)
|
||||
yield bec
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def qtbot_scope_module(qapp, request):
|
||||
"""
|
||||
Fixture used to create a QtBot instance for using during testing.
|
||||
|
||||
Make sure to call addWidget for each top-level widget you create to ensure
|
||||
that they are properly closed after the test ends.
|
||||
"""
|
||||
result = QtBot(request)
|
||||
return result
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def connected_client_gui_obj(qtbot_scope_module, gui_id, bec_client_lib):
|
||||
"""
|
||||
Fixture to create a new BECGuiClient object and start a server in the background.
|
||||
|
||||
This fixture is scoped to the session, meaning it remains alive for all tests in the session.
|
||||
We can use this fixture to create a gui object that is used across multiple tests, and
|
||||
simulate a real-world scenario where the gui is not restarted for each test.
|
||||
"""
|
||||
gui = BECGuiClient(gui_id=gui_id)
|
||||
try:
|
||||
gui.start(wait=True)
|
||||
qtbot_scope_module.waitUntil(lambda: hasattr(gui, "bec"), timeout=5000)
|
||||
yield gui
|
||||
finally:
|
||||
gui.kill_server()
|
||||
667
tests/end-2-end/user_interaction/test_user_interaction_e2e.py
Normal file
667
tests/end-2-end/user_interaction/test_user_interaction_e2e.py
Normal file
@@ -0,0 +1,667 @@
|
||||
"""
|
||||
End-to-end tests single gui instance across the full session.
|
||||
|
||||
Each test will use the same gui instance, simulating a real-world scenario where the gui is not
|
||||
restarted for each test. The interaction is tested through the rpc calls.
|
||||
|
||||
Note: wait_for_namespace_created is a utility method that helps to wait for the namespace to be
|
||||
created in the gui. This is necessary because the rpc calls are asynchronous and the namespace
|
||||
may not be created immediately after the rpc call is made.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from bec_widgets.cli.client import BECDockArea
|
||||
from bec_widgets.cli.rpc.rpc_base import RPCBase, RPCReference
|
||||
|
||||
PYTEST_TIMEOUT = 50
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from bec_widgets.cli import client
|
||||
from bec_widgets.cli.client_utils import BECGuiClient
|
||||
|
||||
# pylint: disable=redefined-outer-name
|
||||
# pylint: disable=too-many-arguments
|
||||
# pylint: disable=protected-access
|
||||
# pylint: disable=unused-variable
|
||||
|
||||
|
||||
def wait_for_namespace_change(
|
||||
qtbot,
|
||||
gui: BECGuiClient,
|
||||
parent_widget: RPCBase | RPCReference,
|
||||
object_name: str,
|
||||
widget_gui_id: str,
|
||||
timeout: float = 10000,
|
||||
exists: bool = True,
|
||||
):
|
||||
"""
|
||||
Utility method to wait for the namespace to be created in the widget.
|
||||
|
||||
Args:
|
||||
qtbot: The qtbot fixture.
|
||||
gui: The client_utils.BECGuiClient 'gui' object from the CLI.
|
||||
parent_widget: The widget that creates a new widget.
|
||||
object_name: The name of the widget that was created. Must appear as attribute in namespace of parent.
|
||||
widget_gui_id: The gui_id of the created widget.
|
||||
timeout: The timeout in milliseconds for the qtbot to wait for changes to appear.
|
||||
exists: If True, wait for the object to be created. If False, wait for the object to be removed.
|
||||
"""
|
||||
# GUI object is not registered in the registry (yet)
|
||||
if parent_widget is gui:
|
||||
|
||||
def check_reference_registered():
|
||||
# Check server registry
|
||||
obj = gui._server_registry.get(widget_gui_id, None)
|
||||
if obj is None:
|
||||
if not exists:
|
||||
return True
|
||||
return False
|
||||
# CHeck Ipython registry
|
||||
obj = gui._ipython_registry.get(widget_gui_id, None)
|
||||
if obj is None:
|
||||
if not exists:
|
||||
return True
|
||||
return False
|
||||
|
||||
else:
|
||||
|
||||
def check_reference_registered():
|
||||
# Check server registry
|
||||
obj = gui._server_registry.get(widget_gui_id, None)
|
||||
if obj is None:
|
||||
if not exists:
|
||||
return True
|
||||
return False
|
||||
# CHeck Ipython registry
|
||||
obj = gui._ipython_registry.get(widget_gui_id, None)
|
||||
if obj is None:
|
||||
if not exists:
|
||||
return True
|
||||
return False
|
||||
# Check reference registry
|
||||
ref = parent_widget._rpc_references.get(widget_gui_id, None)
|
||||
if exists:
|
||||
return ref is not None
|
||||
return ref is None
|
||||
|
||||
try:
|
||||
qtbot.waitUntil(check_reference_registered, timeout=timeout)
|
||||
except Exception as e:
|
||||
raise RuntimeError(
|
||||
f"Timeout waiting for {parent_widget.object_name}.{object_name} to be created."
|
||||
) from e
|
||||
|
||||
|
||||
def create_widget(
|
||||
qtbot, gui: BECGuiClient, widget_cls_name: str
|
||||
) -> tuple[RPCReference, RPCReference]:
|
||||
"""Utility method to create a widget and wait for the namespaces to be created."""
|
||||
if hasattr(gui, "dock_area"):
|
||||
dock_area: client.BECDockArea = gui.dock_area
|
||||
else:
|
||||
dock_area: client.BECDockArea = gui.new(name="dock_area")
|
||||
wait_for_namespace_change(qtbot, gui, gui, dock_area.object_name, dock_area._gui_id)
|
||||
dock: client.BECDock = dock_area.new()
|
||||
wait_for_namespace_change(qtbot, gui, dock_area, dock.object_name, dock._gui_id)
|
||||
widget = dock.new(widget=widget_cls_name)
|
||||
wait_for_namespace_change(qtbot, gui, dock, widget.object_name, widget._gui_id)
|
||||
return dock, widget
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def random_generator_from_seed(request):
|
||||
"""Fixture to get a random seed for the following tests."""
|
||||
seed = request.config.getoption("--random-order-seed").split(":")[-1]
|
||||
try:
|
||||
seed = int(seed)
|
||||
except ValueError: # Should not be required...
|
||||
seed = 42
|
||||
rng = random.Random(seed)
|
||||
yield rng
|
||||
|
||||
|
||||
def maybe_remove_dock_area(qtbot, gui: BECGuiClient, random_int_gen: random.Random):
|
||||
"""Utility method to remove all dock_ares from gui object, likelihood 50%."""
|
||||
random_int = random_int_gen.randint(0, 100)
|
||||
if random_int >= 50:
|
||||
# Needed, reference gets deleted in the gui
|
||||
name = gui.dock_area.object_name
|
||||
gui_id = gui.dock_area._gui_id
|
||||
gui.delete("dock_area")
|
||||
wait_for_namespace_change(
|
||||
qtbot, gui=gui, parent_widget=gui, object_name=name, widget_gui_id=gui_id, exists=False
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.timeout(PYTEST_TIMEOUT)
|
||||
def test_widgets_e2e_abort_button(qtbot, connected_client_gui_obj, random_generator_from_seed):
|
||||
"""Test the AbortButton widget."""
|
||||
gui: BECGuiClient = connected_client_gui_obj
|
||||
bec = gui._client
|
||||
# Create dock_area, dock, widget
|
||||
dock, widget = create_widget(qtbot, gui, gui.available_widgets.AbortButton)
|
||||
dock: client.BECDock
|
||||
widget: client.AbortButton
|
||||
|
||||
# No rpc calls to check so far
|
||||
|
||||
# Try detaching the dock
|
||||
dock.detach()
|
||||
|
||||
# Test removing the widget, or leaving it open for the next test
|
||||
maybe_remove_dock_area(qtbot, gui=gui, random_int_gen=random_generator_from_seed)
|
||||
|
||||
|
||||
@pytest.mark.timeout(PYTEST_TIMEOUT)
|
||||
def test_widgets_e2e_bec_progress_bar(qtbot, connected_client_gui_obj, random_generator_from_seed):
|
||||
"""Test the BECProgressBar widget."""
|
||||
gui = connected_client_gui_obj
|
||||
bec = gui._client
|
||||
# Create dock_area, dock, widget
|
||||
dock, widget = create_widget(qtbot, gui, gui.available_widgets.BECProgressBar)
|
||||
dock: client.BECDock
|
||||
widget: client.BECProgressBar
|
||||
|
||||
# Check rpc calls
|
||||
assert widget.label_template == "$value / $maximum - $percentage %"
|
||||
widget.set_maximum(100)
|
||||
widget.set_minimum(50)
|
||||
widget.set_value(75)
|
||||
|
||||
assert widget._get_label() == "75 / 100 - 50 %"
|
||||
|
||||
# Test removing the widget, or leaving it open for the next test
|
||||
maybe_remove_dock_area(qtbot, gui=gui, random_int_gen=random_generator_from_seed)
|
||||
|
||||
|
||||
@pytest.mark.timeout(PYTEST_TIMEOUT)
|
||||
def test_widgets_e2e_bec_queue(qtbot, connected_client_gui_obj, random_generator_from_seed):
|
||||
"""Test the BECQueue widget."""
|
||||
gui = connected_client_gui_obj
|
||||
bec = gui._client
|
||||
# Create dock_area, dock, widget
|
||||
dock, widget = create_widget(qtbot, gui, gui.available_widgets.BECQueue)
|
||||
dock: client.BECDock
|
||||
widget: client.BECQueue
|
||||
|
||||
# No rpc calls to test so far
|
||||
# maybe we can add an rpc call to check the queue length
|
||||
|
||||
# Test removing the widget, or leaving it open for the next test
|
||||
maybe_remove_dock_area(qtbot, gui=gui, random_int_gen=random_generator_from_seed)
|
||||
|
||||
|
||||
@pytest.mark.timeout(PYTEST_TIMEOUT)
|
||||
def test_widgets_e2e_bec_status_box(qtbot, connected_client_gui_obj, random_generator_from_seed):
|
||||
"""Test the BECStatusBox widget."""
|
||||
gui = connected_client_gui_obj
|
||||
bec = gui._client
|
||||
# Create dock_area, dock, widget
|
||||
dock, widget = create_widget(qtbot, gui, gui.available_widgets.BECStatusBox)
|
||||
|
||||
# Check rpc calls
|
||||
assert widget.get_server_state() in ["RUNNING", "IDLE", "BUSY", "ERROR"]
|
||||
|
||||
# Test removing the widget, or leaving it open for the next test
|
||||
maybe_remove_dock_area(qtbot, gui=gui, random_int_gen=random_generator_from_seed)
|
||||
|
||||
|
||||
@pytest.mark.timeout(PYTEST_TIMEOUT)
|
||||
def test_widgets_e2e_dap_combo_box(qtbot, connected_client_gui_obj, random_generator_from_seed):
|
||||
"""Test the DAPComboBox widget."""
|
||||
gui = connected_client_gui_obj
|
||||
bec = gui._client
|
||||
# Create dock_area, dock, widget
|
||||
dock, widget = create_widget(qtbot, gui, gui.available_widgets.DapComboBox)
|
||||
dock: client.BECDock
|
||||
widget: client.DAPComboBox
|
||||
|
||||
# Check rpc calls
|
||||
widget.select_fit_model("PseudoVoigtModel")
|
||||
widget.select_x_axis("samx")
|
||||
widget.select_y_axis("bpm4i")
|
||||
|
||||
# Test removing the widget, or leaving it open for the next test
|
||||
maybe_remove_dock_area(qtbot, gui=gui, random_int_gen=random_generator_from_seed)
|
||||
|
||||
|
||||
@pytest.mark.timeout(PYTEST_TIMEOUT)
|
||||
def test_widgets_e2e_device_browser(qtbot, connected_client_gui_obj, random_generator_from_seed):
|
||||
"""Test the DeviceBrowser widget."""
|
||||
gui = connected_client_gui_obj
|
||||
bec = gui._client
|
||||
# Create dock_area, dock, widget
|
||||
dock, widget = create_widget(qtbot, gui, gui.available_widgets.DeviceBrowser)
|
||||
dock: client.BECDock
|
||||
widget: client.DeviceBrowser
|
||||
|
||||
# No rpc calls yet to check
|
||||
|
||||
# Test removing the widget, or leaving it open for the next test
|
||||
maybe_remove_dock_area(qtbot, gui=gui, random_int_gen=random_generator_from_seed)
|
||||
|
||||
|
||||
@pytest.mark.timeout(PYTEST_TIMEOUT)
|
||||
def test_widgets_e2e_device_combo_box(qtbot, connected_client_gui_obj, random_generator_from_seed):
|
||||
"""Test the DeviceComboBox widget."""
|
||||
gui = connected_client_gui_obj
|
||||
bec = gui._client
|
||||
# Create dock_area, dock, widget
|
||||
dock, widget = create_widget(qtbot, gui, gui.available_widgets.DeviceComboBox)
|
||||
dock: client.BECDock
|
||||
widget: client.DeviceComboBox
|
||||
|
||||
# No rpc calls to check so far, maybe set_device should be exposed
|
||||
|
||||
# Test removing the widget, or leaving it open for the next test
|
||||
maybe_remove_dock_area(qtbot, gui=gui, random_int_gen=random_generator_from_seed)
|
||||
|
||||
|
||||
@pytest.mark.timeout(PYTEST_TIMEOUT)
|
||||
def test_widgets_e2e_device_line_edit(qtbot, connected_client_gui_obj, random_generator_from_seed):
|
||||
"""Test the DeviceLineEdit widget."""
|
||||
gui = connected_client_gui_obj
|
||||
bec = gui._client
|
||||
# Create dock_area, dock, widget
|
||||
dock, widget = create_widget(qtbot, gui, gui.available_widgets.DeviceLineEdit)
|
||||
dock: client.BECDock
|
||||
widget: client.DeviceLineEdit
|
||||
|
||||
# No rpc calls to check so far
|
||||
# Should probably have a set_device method
|
||||
|
||||
# No rpc calls to check so far, maybe set_device should be exposed
|
||||
|
||||
# Test removing the widget, or leaving it open for the next test
|
||||
maybe_remove_dock_area(qtbot, gui=gui, random_int_gen=random_generator_from_seed)
|
||||
|
||||
|
||||
@pytest.mark.timeout(PYTEST_TIMEOUT)
|
||||
def test_widgets_e2e_image(qtbot, connected_client_gui_obj, random_generator_from_seed):
|
||||
"""Test the Image widget."""
|
||||
gui = connected_client_gui_obj
|
||||
bec = gui._client
|
||||
# Create dock_area, dock, widget
|
||||
dock, widget = create_widget(qtbot, gui, gui.available_widgets.Image)
|
||||
dock: client.BECDock
|
||||
widget: client.Image
|
||||
|
||||
scans = bec.scans
|
||||
dev = bec.device_manager.devices
|
||||
# Test rpc calls
|
||||
img = widget.image(dev.eiger)
|
||||
assert img.get_data() is None
|
||||
# Run a scan and plot the image
|
||||
s = scans.line_scan(dev.samx, -3, 3, steps=50, exp_time=0.01, relative=False)
|
||||
s.wait()
|
||||
|
||||
def _wait_for_scan_in_history():
|
||||
# Get scan item from history
|
||||
scan_item = bec.history.get_by_scan_id(s.scan.scan_id)
|
||||
return scan_item is not None
|
||||
|
||||
qtbot.waitUntil(_wait_for_scan_in_history, timeout=7000)
|
||||
|
||||
# Check that last image is equivalent to data in Redis
|
||||
last_img = bec.device_monitor.get_data(
|
||||
dev.eiger, count=1
|
||||
) # Get last image from Redis monitor 2D endpoint
|
||||
assert np.allclose(img.get_data(), last_img)
|
||||
|
||||
# Test removing the widget, or leaving it open for the next test
|
||||
maybe_remove_dock_area(qtbot, gui=gui, random_int_gen=random_generator_from_seed)
|
||||
|
||||
|
||||
# TODO re-enable when issue is resolved #560
|
||||
# @pytest.mark.timeout(PYTEST_TIMEOUT)
|
||||
# def test_widgets_e2e_log_panel(qtbot, connected_client_gui_obj, random_generator_from_seed):
|
||||
# """Test the LogPanel widget."""
|
||||
# gui = connected_client_gui_obj
|
||||
# bec = gui._client
|
||||
# # Create dock_area, dock, widget
|
||||
# dock, widget = create_widget(qtbot, gui, gui.available_widgets.LogPanel)
|
||||
# dock: client.BECDock
|
||||
# widget: client.LogPanel
|
||||
|
||||
# # No rpc calls to check so far
|
||||
|
||||
# # Test removing the widget, or leaving it open for the next test
|
||||
# maybe_remove_dock_area(qtbot, gui=gui, random_int_gen=random_generator_from_seed)
|
||||
|
||||
|
||||
@pytest.mark.timeout(PYTEST_TIMEOUT)
|
||||
def test_widgets_e2e_minesweeper(qtbot, connected_client_gui_obj, random_generator_from_seed):
|
||||
"""Test the MineSweeper widget."""
|
||||
gui = connected_client_gui_obj
|
||||
bec = gui._client
|
||||
# Create dock_area, dock, widget
|
||||
dock, widget = create_widget(qtbot, gui, gui.available_widgets.Minesweeper)
|
||||
dock: client.BECDock
|
||||
widget: client.MineSweeper
|
||||
|
||||
# No rpc calls to check so far
|
||||
|
||||
# Test removing the widget, or leaving it open for the next test
|
||||
maybe_remove_dock_area(qtbot, gui=gui, random_int_gen=random_generator_from_seed)
|
||||
|
||||
|
||||
@pytest.mark.timeout(PYTEST_TIMEOUT)
|
||||
def test_widgets_e2e_motor_map(qtbot, connected_client_gui_obj, random_generator_from_seed):
|
||||
"""Test the MotorMap widget."""
|
||||
gui = connected_client_gui_obj
|
||||
bec = gui._client
|
||||
# Create dock_area, dock, widget
|
||||
dock, widget = create_widget(qtbot, gui, gui.available_widgets.MotorMap)
|
||||
dock: client.BECDock
|
||||
widget: client.MotorMap
|
||||
|
||||
# Test RPC calls
|
||||
dev = bec.device_manager.devices
|
||||
scans = bec.scans
|
||||
# Set motor map to names
|
||||
widget.map(dev.samx, dev.samy)
|
||||
# Move motor samx to pos
|
||||
pos = dev.samx.limits[1] - 1 # -1 from higher limit
|
||||
scans.mv(dev.samx, pos, relative=False).wait()
|
||||
# Check that data is up to date
|
||||
assert np.isclose(widget.get_data()["x"][-1], pos, dev.samx.precision)
|
||||
# Move motor samy to pos
|
||||
pos = dev.samy.limits[0] + 1 # +1 from lower limit
|
||||
scans.mv(dev.samy, pos, relative=False).wait()
|
||||
# Check that data is up to date
|
||||
assert np.isclose(widget.get_data()["y"][-1], pos, dev.samy.precision)
|
||||
|
||||
# Test removing the widget, or leaving it open for the next test
|
||||
maybe_remove_dock_area(qtbot, gui=gui, random_int_gen=random_generator_from_seed)
|
||||
|
||||
|
||||
@pytest.mark.timeout(PYTEST_TIMEOUT)
|
||||
def test_widgets_e2e_multi_waveform(qtbot, connected_client_gui_obj, random_generator_from_seed):
|
||||
"""Test MultiWaveform widget."""
|
||||
gui = connected_client_gui_obj
|
||||
bec = gui._client
|
||||
# Create dock_area, dock, widget
|
||||
dock, widget = create_widget(qtbot, gui, gui.available_widgets.MultiWaveform)
|
||||
dock: client.BECDock
|
||||
widget: client.MultiWaveform
|
||||
|
||||
# Test RPC calls
|
||||
dev = bec.device_manager.devices
|
||||
scans = bec.scans
|
||||
# test plotting
|
||||
cm = "cividis"
|
||||
widget.plot(dev.waveform, color_palette=cm)
|
||||
assert widget.monitor == dev.waveform.name
|
||||
assert widget.color_palette == cm
|
||||
|
||||
# Scan with BEC
|
||||
s = scans.line_scan(dev.samx, -3, 3, steps=5, exp_time=0.01, relative=False)
|
||||
s.wait()
|
||||
|
||||
def _wait_for_scan_in_history():
|
||||
# Get scan item from history
|
||||
scan_item = bec.history.get_by_scan_id(s.scan.scan_id)
|
||||
return scan_item is not None
|
||||
|
||||
qtbot.waitUntil(_wait_for_scan_in_history, timeout=7000)
|
||||
# Wait for data in history (should be plotted?)
|
||||
|
||||
# TODO how can we check that the data was plotted, implement get_data()
|
||||
|
||||
# Test removing the widget, or leaving it open for the next test
|
||||
maybe_remove_dock_area(qtbot, gui=gui, random_int_gen=random_generator_from_seed)
|
||||
|
||||
|
||||
@pytest.mark.timeout(PYTEST_TIMEOUT)
|
||||
def test_widgets_e2e_positioner_indicator(
|
||||
qtbot, connected_client_gui_obj, random_generator_from_seed
|
||||
):
|
||||
"""Test the PositionIndicator widget."""
|
||||
gui = connected_client_gui_obj
|
||||
bec = gui._client
|
||||
# Create dock_area, dock, widget
|
||||
dock, widget = create_widget(qtbot, gui, gui.available_widgets.PositionIndicator)
|
||||
dock: client.BECDock
|
||||
widget: client.PositionIndicator
|
||||
|
||||
# TODO check what these rpc calls are supposed to do! Issue created #461
|
||||
widget.set_value(5)
|
||||
|
||||
# Test removing the widget, or leaving it open for the next test
|
||||
maybe_remove_dock_area(qtbot, gui=gui, random_int_gen=random_generator_from_seed)
|
||||
|
||||
|
||||
@pytest.mark.timeout(PYTEST_TIMEOUT)
|
||||
def test_widgets_e2e_positioner_box(qtbot, connected_client_gui_obj, random_generator_from_seed):
|
||||
"""Test the PositionerBox widget."""
|
||||
gui = connected_client_gui_obj
|
||||
bec = gui._client
|
||||
# Create dock_area, dock, widget
|
||||
dock, widget = create_widget(qtbot, gui, gui.available_widgets.PositionerBox)
|
||||
dock: client.BECDock
|
||||
widget: client.PositionerBox
|
||||
|
||||
# Test rpc calls
|
||||
dev = bec.device_manager.devices
|
||||
scans = bec.scans
|
||||
# No rpc calls to check so far
|
||||
widget.set_positioner(dev.samx)
|
||||
widget.set_positioner(dev.samy.name)
|
||||
|
||||
scans.mv(dev.samy, -3, relative=False).wait()
|
||||
|
||||
# Test removing the widget, or leaving it open for the next test
|
||||
maybe_remove_dock_area(qtbot, gui=gui, random_int_gen=random_generator_from_seed)
|
||||
|
||||
|
||||
@pytest.mark.timeout(PYTEST_TIMEOUT)
|
||||
def test_widgets_e2e_positioner_box_2d(qtbot, connected_client_gui_obj, random_generator_from_seed):
|
||||
"""Test the PositionerBox2D widget."""
|
||||
gui = connected_client_gui_obj
|
||||
bec = gui._client
|
||||
# Create dock_area, dock, widget
|
||||
dock, widget = create_widget(qtbot, gui, gui.available_widgets.PositionerBox2D)
|
||||
dock: client.BECDock
|
||||
widget: client.PositionerBox2D
|
||||
|
||||
# Test rpc calls
|
||||
dev = bec.device_manager.devices
|
||||
scans = bec.scans
|
||||
# No rpc calls to check so far
|
||||
widget.set_positioner_hor(dev.samx)
|
||||
widget.set_positioner_ver(dev.samy)
|
||||
|
||||
# Try moving the motors
|
||||
scans.mv(dev.samx, 3, relative=False).wait()
|
||||
scans.mv(dev.samy, -3, relative=False).wait()
|
||||
|
||||
# Test removing the widget, or leaving it open for the next test
|
||||
maybe_remove_dock_area(qtbot, gui=gui, random_int_gen=random_generator_from_seed)
|
||||
|
||||
|
||||
@pytest.mark.timeout(PYTEST_TIMEOUT)
|
||||
def test_widgets_e2e_positioner_control_line(
|
||||
qtbot, connected_client_gui_obj, random_generator_from_seed
|
||||
):
|
||||
"""Test the positioner control line widget"""
|
||||
gui = connected_client_gui_obj
|
||||
bec = gui._client
|
||||
# Create dock_area, dock, widget
|
||||
dock, widget = create_widget(qtbot, gui, gui.available_widgets.PositionerControlLine)
|
||||
dock: client.BECDock
|
||||
widget: client.PositionerControlLine
|
||||
|
||||
# Test rpc calls
|
||||
dev = bec.device_manager.devices
|
||||
scans = bec.scans
|
||||
# Set positioner
|
||||
widget.set_positioner(dev.samx)
|
||||
scans.mv(dev.samx, 3, relative=False).wait()
|
||||
widget.set_positioner(dev.samy.name)
|
||||
scans.mv(dev.samy, -3, relative=False).wait()
|
||||
|
||||
# Test removing the widget, or leaving it open for the next test
|
||||
maybe_remove_dock_area(qtbot, gui=gui, random_int_gen=random_generator_from_seed)
|
||||
|
||||
|
||||
@pytest.mark.timeout(PYTEST_TIMEOUT)
|
||||
def test_widgets_e2e_ring_progress_bar(qtbot, connected_client_gui_obj, random_generator_from_seed):
|
||||
"""Test the RingProgressBar widget"""
|
||||
gui = connected_client_gui_obj
|
||||
bec = gui._client
|
||||
# Create dock_area, dock, widget
|
||||
dock, widget = create_widget(qtbot, gui, gui.available_widgets.RingProgressBar)
|
||||
dock: client.BECDock
|
||||
widget: client.RingProgressBar
|
||||
|
||||
# Test rpc calls
|
||||
dev = bec.device_manager.devices
|
||||
scans = bec.scans
|
||||
# Do a scan
|
||||
scans.line_scan(dev.samx, -3, 3, steps=50, exp_time=0.01, relative=False).wait()
|
||||
|
||||
# Test removing the widget, or leaving it open for the next test
|
||||
maybe_remove_dock_area(qtbot, gui=gui, random_int_gen=random_generator_from_seed)
|
||||
|
||||
|
||||
@pytest.mark.timeout(PYTEST_TIMEOUT)
|
||||
def test_widgets_e2e_scan_control(qtbot, connected_client_gui_obj, random_generator_from_seed):
|
||||
"""Test the ScanControl widget"""
|
||||
gui = connected_client_gui_obj
|
||||
bec = gui._client
|
||||
# Create dock_area, dock, widget
|
||||
dock, widget = create_widget(qtbot, gui, gui.available_widgets.ScanControl)
|
||||
dock: client.BECDock
|
||||
widget: client.ScanControl
|
||||
|
||||
# No rpc calls to check so far
|
||||
|
||||
# Test removing the widget, or leaving it open for the next test
|
||||
maybe_remove_dock_area(qtbot, gui=gui, random_int_gen=random_generator_from_seed)
|
||||
|
||||
|
||||
@pytest.mark.timeout(PYTEST_TIMEOUT)
|
||||
def test_widgets_e2e_scatter_waveform(qtbot, connected_client_gui_obj, random_generator_from_seed):
|
||||
"""Test the ScatterWaveform widget"""
|
||||
gui = connected_client_gui_obj
|
||||
bec = gui._client
|
||||
# Create dock_area, dock, widget
|
||||
dock, widget = create_widget(qtbot, gui, gui.available_widgets.ScatterWaveform)
|
||||
dock: client.BECDock
|
||||
widget: client.ScatterWaveform
|
||||
|
||||
# Test rpc calls
|
||||
dev = bec.device_manager.devices
|
||||
scans = bec.scans
|
||||
widget.plot(dev.samx, dev.samy, dev.bpm4i)
|
||||
scans.grid_scan(dev.samx, -5, 5, 5, dev.samy, -5, 5, 5, exp_time=0.01, relative=False).wait()
|
||||
|
||||
# Test removing the widget, or leaving it open for the next test
|
||||
maybe_remove_dock_area(qtbot, gui=gui, random_int_gen=random_generator_from_seed)
|
||||
|
||||
|
||||
@pytest.mark.timeout(PYTEST_TIMEOUT)
|
||||
def test_widgets_e2e_stop_button(qtbot, connected_client_gui_obj, random_generator_from_seed):
|
||||
"""Test the StopButton widget"""
|
||||
gui = connected_client_gui_obj
|
||||
bec = gui._client
|
||||
# Create dock_area, dock, widget
|
||||
dock, widget = create_widget(qtbot, gui, gui.available_widgets.StopButton)
|
||||
dock: client.BECDock
|
||||
widget: client.StopButton
|
||||
|
||||
# No rpc calls to check so far
|
||||
|
||||
# Test removing the widget, or leaving it open for the next test
|
||||
maybe_remove_dock_area(qtbot, gui=gui, random_int_gen=random_generator_from_seed)
|
||||
|
||||
|
||||
@pytest.mark.timeout(PYTEST_TIMEOUT)
|
||||
def test_widgets_e2e_resume_button(qtbot, connected_client_gui_obj, random_generator_from_seed):
|
||||
"""Test the StopButton widget"""
|
||||
gui = connected_client_gui_obj
|
||||
bec = gui._client
|
||||
# Create dock_area, dock, widget
|
||||
dock, widget = create_widget(qtbot, gui, gui.available_widgets.ResumeButton)
|
||||
dock: client.BECDock
|
||||
widget: client.ResumeButton
|
||||
|
||||
# No rpc calls to check so far
|
||||
|
||||
# Test removing the widget, or leaving it open for the next test
|
||||
maybe_remove_dock_area(qtbot, gui=gui, random_int_gen=random_generator_from_seed)
|
||||
|
||||
|
||||
@pytest.mark.timeout(PYTEST_TIMEOUT)
|
||||
def test_widgets_e2e_reset_button(qtbot, connected_client_gui_obj, random_generator_from_seed):
|
||||
"""Test the StopButton widget"""
|
||||
gui = connected_client_gui_obj
|
||||
bec = gui._client
|
||||
# Create dock_area, dock, widget
|
||||
dock, widget = create_widget(qtbot, gui, gui.available_widgets.ResetButton)
|
||||
dock: client.BECDock
|
||||
widget: client.ResetButton
|
||||
# No rpc calls to check so far
|
||||
|
||||
# Test removing the widget, or leaving it open for the next test
|
||||
maybe_remove_dock_area(qtbot, gui=gui, random_int_gen=random_generator_from_seed)
|
||||
|
||||
|
||||
@pytest.mark.timeout(PYTEST_TIMEOUT)
|
||||
def test_widgets_e2e_text_box(qtbot, connected_client_gui_obj, random_generator_from_seed):
|
||||
"""Test the TextBox widget"""
|
||||
gui = connected_client_gui_obj
|
||||
bec = gui._client
|
||||
# Create dock_area, dock, widget
|
||||
dock, widget = create_widget(qtbot, gui, gui.available_widgets.TextBox)
|
||||
dock: client.BECDock
|
||||
widget: client.TextBox
|
||||
|
||||
# RPC calls
|
||||
widget.set_plain_text("Hello World")
|
||||
widget.set_html_text("<b> Hello World HTML </b>")
|
||||
|
||||
# Test removing the widget, or leaving it open for the next test
|
||||
maybe_remove_dock_area(qtbot, gui=gui, random_int_gen=random_generator_from_seed)
|
||||
|
||||
|
||||
@pytest.mark.timeout(PYTEST_TIMEOUT)
|
||||
def test_widgets_e2e_waveform(qtbot, connected_client_gui_obj, random_generator_from_seed):
|
||||
"""Test the Waveform widget"""
|
||||
gui = connected_client_gui_obj
|
||||
bec = gui._client
|
||||
# Create dock_area, dock, widget
|
||||
dock, widget = create_widget(qtbot, gui, gui.available_widgets.Waveform)
|
||||
dock: client.BECDock
|
||||
widget: client.Waveform
|
||||
|
||||
# Test rpc calls
|
||||
dev = bec.device_manager.devices
|
||||
scans = bec.scans
|
||||
widget.plot(dev.bpm4i)
|
||||
s = scans.line_scan(dev.samx, -3, 3, steps=50, exp_time=0.01, relative=False)
|
||||
s.wait()
|
||||
|
||||
def _wait_for_scan_in_history():
|
||||
# Get scan item from history
|
||||
scan_item = bec.history.get_by_scan_id(s.scan.scan_id)
|
||||
return scan_item is not None
|
||||
|
||||
qtbot.waitUntil(_wait_for_scan_in_history, timeout=7000)
|
||||
|
||||
scan_item = bec.history.get_by_scan_id(s.scan.scan_id)
|
||||
samx_data = scan_item.devices.samx.samx.read()["value"]
|
||||
bpm4i_data = scan_item.devices.bpm4i.bpm4i.read()["value"]
|
||||
curve = widget.curves[0]
|
||||
assert np.allclose(curve.get_data()[0], samx_data)
|
||||
assert np.allclose(curve.get_data()[1], bpm4i_data)
|
||||
|
||||
# Test removing the widget, or leaving it open for the next test
|
||||
maybe_remove_dock_area(qtbot, gui=gui, random_int_gen=random_generator_from_seed)
|
||||
@@ -89,6 +89,13 @@ def test_undock_and_dock_docks(bec_dock_area, qtbot):
|
||||
assert len(bec_dock_area.dock_area.tempAreas) == 0
|
||||
|
||||
|
||||
def test_new_dock_raises_for_invalid_name(bec_dock_area):
|
||||
with pytest.raises(ValueError):
|
||||
bec_dock_area.new(
|
||||
name="new", _override_slot_params={"popup_error": False, "raise_error": True}
|
||||
)
|
||||
|
||||
|
||||
###################################
|
||||
# Toolbar Actions
|
||||
###################################
|
||||
|
||||
@@ -74,6 +74,7 @@ def test_client_generator_with_black_formatting():
|
||||
import inspect
|
||||
import traceback
|
||||
from functools import reduce
|
||||
from operator import add
|
||||
from typing import Literal, Optional
|
||||
|
||||
from bec_lib.logger import bec_logger
|
||||
|
||||
@@ -116,7 +116,7 @@ def fill_commponents(components: dict[str, DynamicFormItem]):
|
||||
|
||||
|
||||
def test_griditems_are_correct_class(
|
||||
metadata_widget: tuple[ScanMetadata, dict[str, DynamicFormItem]]
|
||||
metadata_widget: tuple[ScanMetadata, dict[str, DynamicFormItem]],
|
||||
):
|
||||
_, components = metadata_widget
|
||||
assert isinstance(components["sample_name"], StrMetadataField)
|
||||
@@ -162,7 +162,7 @@ def test_validation(metadata_widget: tuple[ScanMetadata, dict[str, DynamicFormIt
|
||||
|
||||
|
||||
def test_numbers_clipped_to_limits(
|
||||
metadata_widget: tuple[ScanMetadata, dict[str, DynamicFormItem]]
|
||||
metadata_widget: tuple[ScanMetadata, dict[str, DynamicFormItem]],
|
||||
):
|
||||
widget, components = metadata_widget = metadata_widget
|
||||
fill_commponents(components)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import json
|
||||
from types import SimpleNamespace
|
||||
from unittest import mock
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import numpy as np
|
||||
@@ -19,6 +21,8 @@ from tests.unit_tests.client_mocks import (
|
||||
|
||||
from .conftest import create_widget
|
||||
|
||||
# pylint: disable=unexpected-keyword-arg
|
||||
|
||||
##################################################
|
||||
# Waveform widget base functionality tests
|
||||
##################################################
|
||||
@@ -541,7 +545,14 @@ def test_on_async_readback_add_update(qtbot, mocked_client):
|
||||
|
||||
msg = {"signals": {"async_device": {"value": [100, 200], "timestamp": [1001, 1002]}}}
|
||||
metadata = {"async_update": {"max_shape": [None], "type": "add"}}
|
||||
wf.on_async_readback(msg, metadata)
|
||||
|
||||
cb_info_ret = {"scan_id": wf.scan_id}
|
||||
|
||||
def ret_sender():
|
||||
return SimpleNamespace(cb_info={"scan_id": wf.scan_id})
|
||||
|
||||
with mock.patch.object(wf, "sender", side_effect=ret_sender):
|
||||
wf.on_async_readback(msg, metadata, _override_slot_params={"verify_sender": False})
|
||||
|
||||
x_data, y_data = c.get_data()
|
||||
assert len(x_data) == 5
|
||||
@@ -553,7 +564,8 @@ def test_on_async_readback_add_update(qtbot, mocked_client):
|
||||
# instruction='replace'
|
||||
msg2 = {"signals": {"async_device": {"value": [999], "timestamp": [555]}}}
|
||||
metadata2 = {"async_update": {"max_shape": [None], "type": "replace"}}
|
||||
wf.on_async_readback(msg2, metadata2)
|
||||
with mock.patch.object(wf, "sender", side_effect=ret_sender):
|
||||
wf.on_async_readback(msg2, metadata2, _override_slot_params={"verify_sender": False})
|
||||
x_data2, y_data2 = c.get_data()
|
||||
np.testing.assert_array_equal(x_data2, [0])
|
||||
|
||||
@@ -568,7 +580,8 @@ def test_on_async_readback_add_update(qtbot, mocked_client):
|
||||
metadata = {
|
||||
"async_update": {"max_shape": [None, waveform_shape], "index": 0, "type": "add_slice"}
|
||||
}
|
||||
wf.on_async_readback(msg, metadata)
|
||||
with mock.patch.object(wf, "sender", side_effect=ret_sender):
|
||||
wf.on_async_readback(msg, metadata, _override_slot_params={"verify_sender": False})
|
||||
|
||||
# Old data should be deleted since the slice_index did not match
|
||||
x_data, y_data = c.get_data()
|
||||
@@ -595,7 +608,8 @@ def test_on_async_readback_add_update(qtbot, mocked_client):
|
||||
metadata = {
|
||||
"async_update": {"max_shape": [None, waveform_shape], "index": 0, "type": "add_slice"}
|
||||
}
|
||||
wf.on_async_readback(msg, metadata)
|
||||
with mock.patch.object(wf, "sender", side_effect=ret_sender):
|
||||
wf.on_async_readback(msg, metadata, _override_slot_params={"verify_sender": False})
|
||||
x_data, y_data = c.get_data()
|
||||
assert len(y_data) == waveform_shape
|
||||
assert len(x_data) == waveform_shape
|
||||
@@ -616,7 +630,8 @@ def test_on_async_readback_add_update(qtbot, mocked_client):
|
||||
}
|
||||
}
|
||||
metadata = {"async_update": {"type": "replace"}}
|
||||
wf.on_async_readback(msg, metadata)
|
||||
with mock.patch.object(wf, "sender", side_effect=ret_sender):
|
||||
wf.on_async_readback(msg, metadata, _override_slot_params={"verify_sender": False})
|
||||
|
||||
x_data, y_data = c.get_data()
|
||||
assert np.array_equal(y_data, np.array(range(waveform_shape)))
|
||||
|
||||
Reference in New Issue
Block a user