mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-04-18 06:15:37 +02:00
Compare commits
45 Commits
v0.58.1
...
feature/st
| Author | SHA1 | Date | |
|---|---|---|---|
| 065edf1e2d | |||
| 9e16f2faf9 | |||
| 2a36d9364f | |||
| 27426ce7a5 | |||
|
|
69adadd6d7 | ||
| 6f96498de6 | |||
| 836b6e64f6 | |||
|
|
fab7dd7eec | ||
| 9263f8ef5c | |||
|
|
658728efef | ||
| 6b8432f5b2 | |||
| bc709c4184 | |||
| b49462abeb | |||
| d9d4e3c9bf | |||
| fe04dd80e5 | |||
|
|
718950cf0d | ||
| 17a0068757 | |||
| abc6caa2d0 | |||
|
|
99fb82561b | ||
| 61ba08d0b8 | |||
| 40b5688158 | |||
|
|
0a4e253cbd | ||
| 6428e38ab9 | |||
| fc4f4f81ad | |||
| f6629852eb | |||
| 3adf6cfd58 | |||
| b15816ca9f | |||
| 6b1d5827d6 | |||
| f0391f59c9 | |||
| 006a0894b8 | |||
| 9c5a471234 | |||
| 1c7f4912ce | |||
| df1be10057 | |||
| 954c576131 | |||
| 867720a897 | |||
| 2b40602bdc | |||
| 11173b9c0a | |||
|
|
52d46e77db | ||
| e7838b0f2f | |||
|
|
2ae3810cf6 | ||
| 178fe4d2da | |||
| 2d79ef8fe5 | |||
| d56c5493cd | |||
| cf6e5a40fc | |||
| 64abd67b9b |
190
.gitlab-ci.yml
190
.gitlab-ci.yml
@@ -22,6 +22,13 @@ workflow:
|
||||
|
||||
include:
|
||||
- template: Security/Secret-Detection.gitlab-ci.yml
|
||||
- project: "bec/awi_utils"
|
||||
file: "/templates/check-packages-job.yml"
|
||||
inputs:
|
||||
stage: test
|
||||
path: "."
|
||||
pytest_args: "-v --random-order tests/"
|
||||
exclude_packages: ""
|
||||
|
||||
# different stages in the pipeline
|
||||
stages:
|
||||
@@ -31,8 +38,22 @@ stages:
|
||||
- End2End
|
||||
- Deploy
|
||||
|
||||
.install-qt-webengine-deps: &install-qt-webengine-deps
|
||||
- apt-get -y install libnss3 libxdamage1 libasound2 libatomic1 libxcursor1
|
||||
- export QTWEBENGINE_DISABLE_SANDBOX=1
|
||||
|
||||
.clone-repos: &clone-repos
|
||||
- git clone --branch $BEC_CORE_BRANCH https://gitlab.psi.ch/bec/bec.git
|
||||
- git clone --branch $OPHYD_DEVICES_BRANCH https://gitlab.psi.ch/bec/ophyd_devices.git
|
||||
- export OHPYD_DEVICES_PATH=$PWD/ophyd_devices
|
||||
|
||||
.install-os-packages: &install-os-packages
|
||||
- apt-get update
|
||||
- apt-get install -y libgl1-mesa-glx libegl1-mesa x11-utils libxkbcommon-x11-0 libdbus-1-3
|
||||
- *install-qt-webengine-deps
|
||||
|
||||
before_script:
|
||||
- if [[ "$CI_PROJECT_PATH" != "bec/bec_widgets" ]]; then
|
||||
- if [[ "$CI_PROJECT_PATH" != "bec/bec_widgets" ]]; then
|
||||
echo -e "\033[35;1m Using branch $CHILD_PIPELINE_BRANCH of BEC Widgets \033[0;m";
|
||||
test -d bec_widgets || git clone --branch $CHILD_PIPELINE_BRANCH https://gitlab.psi.ch/bec/bec_widgets.git; cd bec_widgets;
|
||||
fi
|
||||
@@ -75,18 +96,21 @@ pylint-check:
|
||||
- apt-get update
|
||||
- apt-get install -y bc
|
||||
script:
|
||||
- git fetch origin $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
|
||||
# Identify changed Python files
|
||||
- if [ "$CI_PIPELINE_SOURCE" == "merge_request_event" ]; then
|
||||
TARGET_BRANCH_COMMIT_SHA=$(git rev-parse $CI_MERGE_REQUEST_TARGET_BRANCH_NAME);
|
||||
CHANGED_FILES=$(git diff --name-only $SOURCE_BRANCH_COMMIT_SHA $TARGET_BRANCH_COMMIT_SHA | grep '\.py$' || true);
|
||||
TARGET_BRANCH_COMMIT_SHA=$(git rev-parse origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME);
|
||||
CHANGED_FILES=$(git diff --name-only $TARGET_BRANCH_COMMIT_SHA HEAD | grep '\.py$' || true);
|
||||
else
|
||||
CHANGED_FILES=$(git diff --name-only $CI_COMMIT_BEFORE_SHA $CI_COMMIT_SHA | grep '\.py$' || true);
|
||||
CHANGED_FILES=$(git diff --name-only $CI_COMMIT_BEFORE_SHA $CI_COMMIT_SHA | grep '\.py$' || true);
|
||||
fi
|
||||
- if [ -z "$CHANGED_FILES" ]; then echo "No Python files changed."; exit 0; fi
|
||||
|
||||
- echo "Changed Python files:"
|
||||
- $CHANGED_FILES
|
||||
# Run pylint only on changed files
|
||||
- mkdir ./pylint
|
||||
- pylint $CHANGED_FILES --output-format=text . | tee ./pylint/pylint_changed_files.log || pylint-exit $?
|
||||
- pylint $CHANGED_FILES --output-format=text | tee ./pylint/pylint_changed_files.log || pylint-exit $?
|
||||
- PYLINT_SCORE=$(sed -n 's/^Your code has been rated at \([-0-9.]*\)\/.*/\1/p' ./pylint/pylint_changed_files.log)
|
||||
- echo "Pylint score is $PYLINT_SCORE"
|
||||
|
||||
@@ -103,14 +127,12 @@ tests:
|
||||
stage: test
|
||||
needs: []
|
||||
variables:
|
||||
QT_QPA_PLATFORM: "offscreen"
|
||||
QT_QPA_PLATFORM: "offscreen"
|
||||
script:
|
||||
- git clone --branch $BEC_CORE_BRANCH https://gitlab.psi.ch/bec/bec.git
|
||||
- git clone --branch $OPHYD_DEVICES_BRANCH https://gitlab.psi.ch/bec/ophyd_devices.git
|
||||
- export OHPYD_DEVICES_PATH=$PWD/ophyd_devices
|
||||
- apt-get update
|
||||
- apt-get install -y libgl1-mesa-glx libegl1-mesa x11-utils libxkbcommon-x11-0 libdbus-1-3
|
||||
- *clone-repos
|
||||
- *install-os-packages
|
||||
- pip install -e ./bec/bec_lib[dev]
|
||||
- pip install -e ./bec/bec_ipython_client
|
||||
- pip install -e .[dev,pyqt6]
|
||||
- coverage run --source=./bec_widgets -m pytest -v --junitxml=report.xml --random-order --full-trace ./tests/unit_tests
|
||||
- coverage report
|
||||
@@ -123,123 +145,31 @@ tests:
|
||||
coverage_format: cobertura
|
||||
path: coverage.xml
|
||||
|
||||
tests-3.10-pyside6:
|
||||
extends: "tests"
|
||||
stage: AdditionalTests
|
||||
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:3.10
|
||||
script:
|
||||
- git clone --branch $BEC_CORE_BRANCH https://gitlab.psi.ch/bec/bec.git
|
||||
- git clone --branch $OPHYD_DEVICES_BRANCH https://gitlab.psi.ch/bec/ophyd_devices.git
|
||||
- export OHPYD_DEVICES_PATH=$PWD/ophyd_devices
|
||||
- apt-get update
|
||||
- apt-get install -y libgl1-mesa-glx libegl1-mesa x11-utils libxkbcommon-x11-0 libdbus-1-3
|
||||
- pip install -e ./bec/bec_lib[dev]
|
||||
- pip install -e .[dev,pyside6]
|
||||
- pytest -v --junitxml=report.xml --random-order ./tests/unit_tests
|
||||
allow_failure: true
|
||||
test-matrix:
|
||||
parallel:
|
||||
matrix:
|
||||
- PYTHON_VERSION:
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
QT_PCKG:
|
||||
- "pyside6"
|
||||
- "pyqt5"
|
||||
- "pyqt6"
|
||||
|
||||
tests-3.12-pyside6:
|
||||
extends: "tests"
|
||||
stage: AdditionalTests
|
||||
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:3.12
|
||||
needs: []
|
||||
variables:
|
||||
QT_QPA_PLATFORM: "offscreen"
|
||||
PYTHON_VERSION: ""
|
||||
QT_PCKG: ""
|
||||
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:$PYTHON_VERSION
|
||||
script:
|
||||
- git clone --branch $BEC_CORE_BRANCH https://gitlab.psi.ch/bec/bec.git
|
||||
- git clone --branch $OPHYD_DEVICES_BRANCH https://gitlab.psi.ch/bec/ophyd_devices.git
|
||||
- export OHPYD_DEVICES_PATH=$PWD/ophyd_devices
|
||||
- apt-get update
|
||||
- apt-get install -y libgl1-mesa-glx libegl1-mesa x11-utils libxkbcommon-x11-0 libdbus-1-3
|
||||
- *clone-repos
|
||||
- *install-os-packages
|
||||
- pip install -e ./bec/bec_lib[dev]
|
||||
- pip install -e .[dev,pyside6]
|
||||
- pytest -v --junitxml=report.xml --random-order ./tests/unit_tests
|
||||
allow_failure: true
|
||||
|
||||
tests-3.10-pyqt5:
|
||||
extends: "tests"
|
||||
stage: AdditionalTests
|
||||
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:3.10
|
||||
script:
|
||||
- git clone --branch $BEC_CORE_BRANCH https://gitlab.psi.ch/bec/bec.git
|
||||
- git clone --branch $OPHYD_DEVICES_BRANCH https://gitlab.psi.ch/bec/ophyd_devices.git
|
||||
- export OHPYD_DEVICES_PATH=$PWD/ophyd_devices
|
||||
- apt-get update
|
||||
- apt-get install -y libgl1-mesa-glx libegl1-mesa x11-utils libxkbcommon-x11-0 libdbus-1-3
|
||||
- pip install -e ./bec/bec_lib[dev]
|
||||
- pip install -e .[dev,pyqt5]
|
||||
- pytest -v --junitxml=report.xml --random-order ./tests/unit_tests
|
||||
allow_failure: true
|
||||
|
||||
tests-3.11-pyqt5:
|
||||
extends: "tests"
|
||||
stage: AdditionalTests
|
||||
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:3.11
|
||||
script:
|
||||
- git clone --branch $BEC_CORE_BRANCH https://gitlab.psi.ch/bec/bec.git
|
||||
- git clone --branch $OPHYD_DEVICES_BRANCH https://gitlab.psi.ch/bec/ophyd_devices.git
|
||||
- export OHPYD_DEVICES_PATH=$PWD/ophyd_devices
|
||||
- apt-get update
|
||||
- apt-get install -y libgl1-mesa-glx libegl1-mesa x11-utils libxkbcommon-x11-0 libdbus-1-3
|
||||
- pip install -e ./bec/bec_lib[dev]
|
||||
- pip install -e .[dev,pyqt5]
|
||||
- pytest -v --junitxml=report.xml --random-order ./tests/unit_tests
|
||||
allow_failure: true
|
||||
|
||||
tests-3.12-pyqt5:
|
||||
extends: "tests"
|
||||
stage: AdditionalTests
|
||||
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:3.12
|
||||
script:
|
||||
- git clone --branch $BEC_CORE_BRANCH https://gitlab.psi.ch/bec/bec.git
|
||||
- git clone --branch $OPHYD_DEVICES_BRANCH https://gitlab.psi.ch/bec/ophyd_devices.git
|
||||
- export OHPYD_DEVICES_PATH=$PWD/ophyd_devices
|
||||
- apt-get update
|
||||
- apt-get install -y libgl1-mesa-glx libegl1-mesa x11-utils libxkbcommon-x11-0 libdbus-1-3
|
||||
- pip install -e ./bec/bec_lib[dev]
|
||||
- pip install -e .[dev,pyqt5]
|
||||
- pytest -v --junitxml=report.xml --random-order ./tests/unit_tests
|
||||
allow_failure: true
|
||||
|
||||
tests-3.10-pyqt6:
|
||||
extends: "tests"
|
||||
stage: AdditionalTests
|
||||
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:3.10
|
||||
script:
|
||||
- git clone --branch $BEC_CORE_BRANCH https://gitlab.psi.ch/bec/bec.git
|
||||
- git clone --branch $OPHYD_DEVICES_BRANCH https://gitlab.psi.ch/bec/ophyd_devices.git
|
||||
- export OHPYD_DEVICES_PATH=$PWD/ophyd_devices
|
||||
- apt-get update
|
||||
- apt-get install -y libgl1-mesa-glx libegl1-mesa x11-utils libxkbcommon-x11-0 libdbus-1-3
|
||||
- pip install -e ./bec/bec_lib[dev]
|
||||
- pip install -e .[dev,pyqt6]
|
||||
- pytest -v --junitxml=report.xml --random-order ./tests/unit_tests
|
||||
allow_failure: true
|
||||
|
||||
tests-3.11-pyqt6:
|
||||
extends: "tests"
|
||||
stage: AdditionalTests
|
||||
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:3.11
|
||||
script:
|
||||
- git clone --branch $BEC_CORE_BRANCH https://gitlab.psi.ch/bec/bec.git
|
||||
- git clone --branch $OPHYD_DEVICES_BRANCH https://gitlab.psi.ch/bec/ophyd_devices.git
|
||||
- export OHPYD_DEVICES_PATH=$PWD/ophyd_devices
|
||||
- apt-get update
|
||||
- apt-get install -y libgl1-mesa-glx libegl1-mesa x11-utils libxkbcommon-x11-0 libdbus-1-3
|
||||
- pip install -e ./bec/bec_lib[dev]
|
||||
- pip install -e .[dev,pyqt6]
|
||||
- pytest -v --junitxml=report.xml --random-order ./tests/unit_tests
|
||||
allow_failure: true
|
||||
|
||||
tests-3.12-pyqt6:
|
||||
extends: "tests"
|
||||
stage: AdditionalTests
|
||||
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:3.12
|
||||
script:
|
||||
- git clone --branch $BEC_CORE_BRANCH https://gitlab.psi.ch/bec/bec.git
|
||||
- git clone --branch $OPHYD_DEVICES_BRANCH https://gitlab.psi.ch/bec/ophyd_devices.git
|
||||
- export OHPYD_DEVICES_PATH=$PWD/ophyd_devices
|
||||
- apt-get update
|
||||
- apt-get install -y libgl1-mesa-glx libegl1-mesa x11-utils libxkbcommon-x11-0 libdbus-1-3
|
||||
- pip install -e ./bec/bec_lib[dev]
|
||||
- pip install -e .[dev,pyqt6]
|
||||
- pip install -e ./bec/bec_ipython_client
|
||||
- pip install -e .[dev,$QT_PCKG]
|
||||
- pytest -v --junitxml=report.xml --random-order ./tests/unit_tests
|
||||
allow_failure: true
|
||||
|
||||
@@ -251,8 +181,8 @@ end-2-end-conda:
|
||||
variables:
|
||||
QT_QPA_PLATFORM: "offscreen"
|
||||
script:
|
||||
- apt-get update
|
||||
- apt-get install -y libgl1-mesa-glx libegl1-mesa x11-utils libxkbcommon-x11-0 libdbus-1-3
|
||||
- *clone-repos
|
||||
- *install-os-packages
|
||||
- conda config --prepend channels conda-forge
|
||||
- conda config --set channel_priority strict
|
||||
- conda config --set always_yes yes --set changeps1 no
|
||||
@@ -261,10 +191,6 @@ end-2-end-conda:
|
||||
- source ~/.bashrc
|
||||
- conda activate test-environment
|
||||
|
||||
- git clone --branch $BEC_CORE_BRANCH https://gitlab.psi.ch/bec/bec.git
|
||||
- git clone --branch $OPHYD_DEVICES_BRANCH https://gitlab.psi.ch/bec/ophyd_devices.git
|
||||
- export OHPYD_DEVICES_PATH=$PWD/ophyd_devices
|
||||
|
||||
- cd ./bec
|
||||
- source ./bin/install_bec_dev.sh -t
|
||||
|
||||
@@ -307,7 +233,7 @@ semver:
|
||||
- pip install python-semantic-release==9.* wheel build twine
|
||||
- export GL_TOKEN=$CI_UPDATES
|
||||
- semantic-release -vv version
|
||||
|
||||
|
||||
# check if any artifacts were created
|
||||
- if [ ! -d dist ]; then echo No release will be made; exit 0; fi
|
||||
- twine upload dist/* -u __token__ -p $CI_PYPI_TOKEN --skip-existing
|
||||
@@ -323,7 +249,7 @@ pages:
|
||||
variables:
|
||||
TARGET_BRANCH: $CI_COMMIT_REF_NAME
|
||||
rules:
|
||||
- if: '$CI_COMMIT_TAG != null'
|
||||
- if: "$CI_COMMIT_TAG != null"
|
||||
variables:
|
||||
TARGET_BRANCH: $CI_COMMIT_TAG
|
||||
- if: '$CI_COMMIT_REF_NAME == "main" && $CI_PROJECT_PATH == "bec/bec_widgets"'
|
||||
|
||||
270
CHANGELOG.md
270
CHANGELOG.md
@@ -2,6 +2,149 @@
|
||||
|
||||
|
||||
|
||||
## v0.63.2 (2024-06-14)
|
||||
|
||||
### Fix
|
||||
|
||||
* fix: do not import "server" in client, prevents from having trouble with QApplication creation order
|
||||
|
||||
Like with QtWebEngine ([`6f96498`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6f96498de66358b89f3a2035627eed2e02dde5a1))
|
||||
|
||||
### Unknown
|
||||
|
||||
* Reapply "feat: implement non-polling, interruptible waiting of gui instruction response with timeout"
|
||||
|
||||
This reverts commit fe04dd80e59a0e74f7fdea603e0642707ecc7c2a. ([`836b6e6`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/836b6e64f694916d6b6f909dedf11a4a6d2c86a4))
|
||||
|
||||
|
||||
## v0.63.1 (2024-06-13)
|
||||
|
||||
### Fix
|
||||
|
||||
* fix: just terminate the remote process in close() instead of communicating
|
||||
|
||||
The proper finalization sequence will be executed by the remote process
|
||||
on SIGTERM ([`9263f8e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/9263f8ef5c17ae7a007a1a564baf787b39061756))
|
||||
|
||||
|
||||
## v0.63.0 (2024-06-13)
|
||||
|
||||
### Documentation
|
||||
|
||||
* docs: add documentation ([`bc709c4`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/bc709c4184c985d4e721f9ea7d1b3dad5e9153a7))
|
||||
|
||||
### Feature
|
||||
|
||||
* feat: add textbox widget ([`d9d4e3c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/d9d4e3c9bf73ab2a5629c2867b50fc91e69489ec))
|
||||
|
||||
### Refactor
|
||||
|
||||
* refactor: add pydantic config, add change_theme ([`6b8432f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6b8432f5b20a71175a3537b5f6832b76e3b67d73))
|
||||
|
||||
### Test
|
||||
|
||||
* test: add test for text box ([`b49462a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b49462abeb186e56bac79d2ef0b0add1ef28a1a5))
|
||||
|
||||
### Unknown
|
||||
|
||||
* Revert "feat: implement non-polling, interruptible waiting of gui instruction response with timeout"
|
||||
|
||||
This reverts commit abc6caa2d0b6141dfbe1f3d025f78ae14deddcb3 ([`fe04dd8`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/fe04dd80e59a0e74f7fdea603e0642707ecc7c2a))
|
||||
|
||||
|
||||
## v0.62.0 (2024-06-12)
|
||||
|
||||
### Feature
|
||||
|
||||
* feat: implement non-polling, interruptible waiting of gui instruction response with timeout ([`abc6caa`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/abc6caa2d0b6141dfbe1f3d025f78ae14deddcb3))
|
||||
|
||||
### Unknown
|
||||
|
||||
* doc: add documentation about creating custom GUI applications embedding BEC Widgets ([`17a0068`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/17a00687579f5efab1990cd83862ec0e78198633))
|
||||
|
||||
|
||||
## v0.61.0 (2024-06-12)
|
||||
|
||||
### Feature
|
||||
|
||||
* feat(widgets/stop_button): General stop button added ([`61ba08d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/61ba08d0b8df9f48f5c54c7c2b4e6d395206e7e6))
|
||||
|
||||
### Refactor
|
||||
|
||||
* refactor: improve labe of auto_update script ([`40b5688`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/40b568815893cd41af3531bb2e647ca1e2e315f4))
|
||||
|
||||
|
||||
## v0.60.0 (2024-06-08)
|
||||
|
||||
### Ci
|
||||
|
||||
* ci: added git fetch for target branch ([`fc4f4f8`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/fc4f4f81ad1be99cf5112f2188a46c5bed2679ee))
|
||||
|
||||
* ci: fixed pylint-check ([`6b1d582`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6b1d5827d6599f06a3acd316060a8d25f0686d54))
|
||||
|
||||
* ci: cleanup ([`11173b9`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/11173b9c0a7dc4b36e35962042e5b86407da49f1))
|
||||
|
||||
### Feature
|
||||
|
||||
* feat: added isort to bw-generate-cli ([`f0391f5`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f0391f59c9eb0a51b693fccfe2e399e869d35dda))
|
||||
|
||||
* feat: added entry point for bw-generate-cli ([`1c7f491`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1c7f4912ce5998e666276969bf4af8656d619a91))
|
||||
|
||||
* feat(cli): auto-discover rpc-enabled widgets ([`df1be10`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/df1be10057a5e85a3f35bef1c1b27366b6727276))
|
||||
|
||||
### Fix
|
||||
|
||||
* fix: removed BECConnector from rpc client interface ([`6428e38`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6428e38ab94c15a2c904e75cc6404bb6d0394e04))
|
||||
|
||||
* fix: added bec_ipython_client as dependency; needed for jupyter widget ([`006a089`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/006a0894b85cba3b2773737ed6fe3e92c81cdee0))
|
||||
|
||||
* fix(BECFigure): removed duplicated user access for plot ([`954c576`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/954c576131f7deac669ddf9f51eeb1d41b6f92b7))
|
||||
|
||||
* fix(bec_connector): field validator should be a classmethod ([`867720a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/867720a897b6713bd0df9af71ffdd11a6a380f7d))
|
||||
|
||||
### Refactor
|
||||
|
||||
* refactor: minor cleanup ([`3adf6cf`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3adf6cfd586355c8b8ce7fdc9722f868e22287c5))
|
||||
|
||||
* refactor: disabled pylint for auto-gen client ([`b15816c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b15816ca9fd3e4ae87cca5fcfe029b4dfca570ca))
|
||||
|
||||
* refactor(isort): added bec_widgets as known first party package ([`9c5a471`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/9c5a471234ed2928e4527b079436db2a807c5f6f))
|
||||
|
||||
* refactor(dock): parent_dock_area changed to orig_area (native for pyqtgraph) ([`2b40602`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/2b40602bdc593ece0447ec926c2100414bd5cf67))
|
||||
|
||||
### Test
|
||||
|
||||
* test: added missing pylint statement to header ([`f662985`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f6629852ebc2b4ee239fa560cc310a5ae2627cf7))
|
||||
|
||||
|
||||
## v0.59.1 (2024-06-07)
|
||||
|
||||
### Fix
|
||||
|
||||
* fix(curve): set_color_map_z typo fixed in user access ([`e7838b0`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/e7838b0f2fc23b0a232ed7d68fbd7f3493a91b9e))
|
||||
|
||||
|
||||
## v0.59.0 (2024-06-07)
|
||||
|
||||
### Build
|
||||
|
||||
* build: added webengine dependency ([`d56c549`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/d56c5493cd28f379d04a79d90b01c73b0760da1b))
|
||||
|
||||
### Ci
|
||||
|
||||
* ci: merged additional tests to parallel matrix job ([`178fe4d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/178fe4d2da3a959f7cd90e7ea0f47314dc1ef4ed))
|
||||
|
||||
* ci: added webengine dependencies ([`2d79ef8`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/2d79ef8fe5e52c61f4a78782770377cd6b41958b))
|
||||
|
||||
### Documentation
|
||||
|
||||
* docs: added website docs ([`cf6e5a4`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/cf6e5a40fc8320e9898a446a5bf14b77e94ef013))
|
||||
|
||||
### Feature
|
||||
|
||||
* feat(widget): added simple website widget with rpc ([`64abd67`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/64abd67b9b416bff9c89880b248d6e8639aa1e70))
|
||||
|
||||
|
||||
## v0.58.1 (2024-06-07)
|
||||
|
||||
### Fix
|
||||
@@ -31,130 +174,3 @@
|
||||
|
||||
|
||||
## v0.57.7 (2024-06-07)
|
||||
|
||||
### Documentation
|
||||
|
||||
* docs: added schema of BECDockArea and BECFigure ([`828067f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/828067f486a905eb4678538df58e2bdd6c770de1))
|
||||
|
||||
### Fix
|
||||
|
||||
* fix: add model_config to pydantic models to allow runtime checks after creation ([`ca5e8d2`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/ca5e8d2fbbffbf221cc5472710fef81a33ee29d6))
|
||||
|
||||
|
||||
## v0.57.6 (2024-06-06)
|
||||
|
||||
### Fix
|
||||
|
||||
* fix(bar): docstrings extended ([`edb1775`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/edb1775967c3ff0723d0edad2b764f1ffc832b7c))
|
||||
|
||||
|
||||
## v0.57.5 (2024-06-06)
|
||||
|
||||
### Documentation
|
||||
|
||||
* docs(figure): docs adjusted to be compatible with new signature ([`c037b87`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/c037b87675af91b26e8c7c60e76622d4ed4cf5d5))
|
||||
|
||||
### Fix
|
||||
|
||||
* fix(waveform): added .plot method with the same signature as BECFigure.plot ([`8479caf`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/8479caf53a7325788ca264e5bd9aee01f1d4c5a0))
|
||||
|
||||
* fix(plot_base): .plot removed from plot_base.py, because there is no use case for it ([`82e2c89`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/82e2c898d2e26f786b2d481f85c647472675e75b))
|
||||
|
||||
### Refactor
|
||||
|
||||
* refactor(figure): logic for .add_image and .image consolidated; logic for .add_plot and .plot consolidated ([`52bc322`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/52bc322b2b8d3ef92ff3480e61bddaf32464f976))
|
||||
|
||||
|
||||
## v0.57.4 (2024-06-06)
|
||||
|
||||
### Fix
|
||||
|
||||
* fix(docks): set_title do update dock internal _name now ([`15cbc21`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/15cbc21e5bb3cf85f5822d44a2b3665b5aa2f346))
|
||||
|
||||
* fix(docks): docks widget_list adn dockarea panels return values fixed ([`ffae5ee`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/ffae5ee54e6b43da660131092452adff195ba4fb))
|
||||
|
||||
|
||||
## v0.57.3 (2024-06-06)
|
||||
|
||||
### Documentation
|
||||
|
||||
* docs(bar): docs updated ([`4be0d14`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4be0d14b7445c2322c2aef86257db168a841265c))
|
||||
|
||||
* docs: fixed syntax of add_widget ([`a951ebf`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a951ebf1be6c086d094aa8abef5e0dfd1b3b8558))
|
||||
|
||||
* docs: added auto update; closes #206 ([`32da803`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/32da803df9f7259842c43e85ba9a0ce29a266d06))
|
||||
|
||||
* docs: cleanup ([`07d60cf`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/07d60cf7355d2edadb3c5ef8b86607d74b360455))
|
||||
|
||||
### Fix
|
||||
|
||||
* fix(ring): automatic updates are disabled uf user specify updates manually with .set_update; 'scan_progres' do not reset number of rings ([`e883dba`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/e883dbad814dbcc0a19c341041c6d836e58a5918))
|
||||
|
||||
* fix(ring): enable_auto_updates(True) do not reset properties of already setup bars ([`a2abad3`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a2abad344f4c0039516eb60a825afb6822c5b19a))
|
||||
|
||||
* fix(ring): set_min_max accepts floats ([`d44b1cf`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/d44b1cf8b107cf02deedd9154b77d01c7f9ed05d))
|
||||
|
||||
* fix(ring): set_update changed to Literals, no need to specify endpoint manually ([`c5b6499`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/c5b6499e41eb1495bf260436ca3e1b036182c360))
|
||||
|
||||
|
||||
## v0.57.2 (2024-06-06)
|
||||
|
||||
### Fix
|
||||
|
||||
* fix(test/e2e): autoupdate e2e rewritten ([`e1af5ca`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/e1af5ca60f0616835f9f41d84412f29dc298c644))
|
||||
|
||||
* fix(test/e2e): spiral_progress_bar e2e tests rewritten to use config_dict ([`7fb31fc`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/7fb31fc4d762ff4ca839971b3092a084186f81b8))
|
||||
|
||||
* fix(test/e2e): dockarea and dock e2e tests changed to check asserts against config_dict ([`5c6ba65`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/5c6ba65469863ea1e6fc5abdc742650e20eba9b9))
|
||||
|
||||
* fix: rpc_server_dock fixture now spawns the server process ([`cd9fc46`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/cd9fc46ff8a947242c8c28adcd73d7de60b11c44))
|
||||
|
||||
* fix: accept scalars or numpy arrays of 1 element ([`2a88e17`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/2a88e17b23436c55d25b7d3449e4af3a7689661c))
|
||||
|
||||
### Refactor
|
||||
|
||||
* refactor: move _get_output and _start_plot_process at the module level ([`69f4371`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/69f4371007c66aee6b7521a6803054025adf8c92))
|
||||
|
||||
|
||||
## v0.57.1 (2024-06-06)
|
||||
|
||||
### Documentation
|
||||
|
||||
* docs: docs refactored from add_widget_bec to add_widget ([`c3f4845`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/c3f4845b4f95005ff737fed5542600b0b9a9cc2b))
|
||||
|
||||
### Fix
|
||||
|
||||
* fix: tests references to add_widget_bec refactored ([`f51b25f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f51b25f0af4ab8b0a75ee48a40bfbb079c16e9d1))
|
||||
|
||||
* fix(dock): add_widget and add_widget_bec consolidated ([`8ae323f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/8ae323f5c3c0d9d0f202d31d5e8374a272a26be2))
|
||||
|
||||
|
||||
## v0.57.0 (2024-06-05)
|
||||
|
||||
### Documentation
|
||||
|
||||
* docs: extend user documentation for BEC Widgets ([`4160f3d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4160f3d6d7ec1122785b5e3fdfc4afe67a95e9a1))
|
||||
|
||||
### Feature
|
||||
|
||||
* feat(widgets/console): BECJupyterConsole added ([`8c03034`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/8c03034acf6b3ed1e346ebf1b785d41068513cc5))
|
||||
|
||||
|
||||
## v0.56.3 (2024-06-05)
|
||||
|
||||
### Ci
|
||||
|
||||
* ci: increased verbosity for e2e tests ([`4af1abe`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4af1abe4e15b62d2f7e70bf987a1a7d8694ef4d5))
|
||||
|
||||
### Fix
|
||||
|
||||
* fix: fixed support for auto updates ([`131f49d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/131f49da8ea65af4d44b50e81c1acfc29cd92093))
|
||||
|
||||
|
||||
## v0.56.2 (2024-06-05)
|
||||
|
||||
### Fix
|
||||
|
||||
* fix(bar): ring saves current value in config ([`9648e3e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/9648e3ea96a4109be6be694d855151ed6d3ad661))
|
||||
|
||||
* fix(dock): dock saves configs of all children widgets ([`4be756a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4be756a8676421c3a3451458995232407295df84))
|
||||
|
||||
@@ -1 +1 @@
|
||||
from .client import BECDockArea, BECFigure
|
||||
from .client import *
|
||||
|
||||
@@ -118,7 +118,7 @@ class AutoUpdates:
|
||||
if not dev_y:
|
||||
return
|
||||
fig.clear_all()
|
||||
plt = fig.plot(x_name=dev_x, y_name=dev_y)
|
||||
plt = fig.plot(x_name=dev_x, y_name=dev_y, label=f"Scan {info.scan_number} - {dev_y}")
|
||||
plt.set(title=f"Scan {info.scan_number}", x_label=dev_x, y_label=dev_y)
|
||||
|
||||
def simple_grid_scan(self, info: ScanInfo) -> None:
|
||||
@@ -132,7 +132,9 @@ class AutoUpdates:
|
||||
dev_y = info.scan_report_devices[1]
|
||||
dev_z = self.get_selected_device(info.monitored_devices, self.gui.selected_device)
|
||||
fig.clear_all()
|
||||
plt = fig.plot(x_name=dev_x, y_name=dev_y, z_name=dev_z, label=f"Scan {info.scan_number}")
|
||||
plt = fig.plot(
|
||||
x_name=dev_x, y_name=dev_y, z_name=dev_z, label=f"Scan {info.scan_number} - {dev_z}"
|
||||
)
|
||||
plt.set(title=f"Scan {info.scan_number}", x_label=dev_x, y_label=dev_y)
|
||||
|
||||
def best_effort(self, info: ScanInfo) -> None:
|
||||
@@ -147,5 +149,5 @@ class AutoUpdates:
|
||||
if not dev_y:
|
||||
return
|
||||
fig.clear_all()
|
||||
plt = fig.plot(x_name=dev_x, y_name=dev_y, label=f"Scan {info.scan_number}")
|
||||
plt = fig.plot(x_name=dev_x, y_name=dev_y, label=f"Scan {info.scan_number} - {dev_y}")
|
||||
plt.set(title=f"Scan {info.scan_number}", x_label=dev_x, y_label=dev_y)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@ from typing import TYPE_CHECKING
|
||||
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
from bec_lib.utils.import_utils import isinstance_based_on_class_name, lazy_import, lazy_import_from
|
||||
from qtpy.QtCore import QCoreApplication
|
||||
from qtpy.QtCore import QEventLoop, QSocketNotifier, QTimer
|
||||
|
||||
import bec_widgets.cli.client as client
|
||||
from bec_widgets.cli.auto_updates import AutoUpdates
|
||||
@@ -24,6 +24,8 @@ if TYPE_CHECKING:
|
||||
|
||||
from bec_widgets.cli.client import BECDockArea, BECFigure
|
||||
|
||||
from bec_lib.serialization import MsgpackSerialization
|
||||
|
||||
messages = lazy_import("bec_lib.messages")
|
||||
# from bec_lib.connector import MessageObject
|
||||
MessageObject = lazy_import_from("bec_lib.connector", ("MessageObject",))
|
||||
@@ -84,13 +86,8 @@ def _start_plot_process(gui_id, gui_class, config) -> None:
|
||||
Start the plot in a new process.
|
||||
"""
|
||||
# pylint: disable=subprocess-run-check
|
||||
monitor_module = importlib.import_module("bec_widgets.cli.server")
|
||||
monitor_path = monitor_module.__file__
|
||||
|
||||
command = [
|
||||
sys.executable,
|
||||
"-u",
|
||||
monitor_path,
|
||||
"bec-gui-server",
|
||||
"--id",
|
||||
gui_id,
|
||||
"--config",
|
||||
@@ -98,7 +95,11 @@ def _start_plot_process(gui_id, gui_class, config) -> None:
|
||||
"--gui_class",
|
||||
gui_class.__name__,
|
||||
]
|
||||
process = subprocess.Popen(command, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
env_dict = os.environ.copy()
|
||||
env_dict["PYTHONUNBUFFERED"] = "1"
|
||||
process = subprocess.Popen(
|
||||
command, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env_dict
|
||||
)
|
||||
process_output_processing_thread = threading.Thread(target=_get_output, args=(process,))
|
||||
process_output_processing_thread.start()
|
||||
return process, process_output_processing_thread
|
||||
@@ -174,16 +175,11 @@ class BECGuiClientMixin:
|
||||
"""
|
||||
Close the figure.
|
||||
"""
|
||||
if self._process is None:
|
||||
return
|
||||
if self.gui_is_alive():
|
||||
self._run_rpc("close", (), wait_for_rpc_response=True)
|
||||
else:
|
||||
self._run_rpc("close", (), wait_for_rpc_response=False)
|
||||
self._process.terminate()
|
||||
self._process_output_processing_thread.join()
|
||||
self._process = None
|
||||
self._client.shutdown()
|
||||
if self._process:
|
||||
self._process.terminate()
|
||||
self._process_output_processing_thread.join()
|
||||
self._process = None
|
||||
|
||||
def print_log(self) -> None:
|
||||
"""
|
||||
@@ -205,6 +201,48 @@ class RPCResponseTimeoutError(Exception):
|
||||
)
|
||||
|
||||
|
||||
class QtRedisMessageWaiter:
|
||||
def __init__(self, redis_connector, message_to_wait):
|
||||
self.ev_loop = QEventLoop()
|
||||
self.response = None
|
||||
self.connector = redis_connector
|
||||
self.message_to_wait = message_to_wait
|
||||
self.pubsub = redis_connector._redis_conn.pubsub()
|
||||
self.pubsub.subscribe(self.message_to_wait.endpoint)
|
||||
fd = self.pubsub.connection._sock.fileno()
|
||||
self.notifier = QSocketNotifier(fd, QSocketNotifier.Read)
|
||||
self.notifier.activated.connect(self._pubsub_readable)
|
||||
|
||||
def _msg_received(self, msg_obj):
|
||||
self.response = msg_obj.value
|
||||
self.ev_loop.quit()
|
||||
|
||||
def wait(self, timeout=1):
|
||||
timer = QTimer()
|
||||
timer.singleShot(timeout * 1000, self.ev_loop.quit)
|
||||
self.ev_loop.exec_()
|
||||
timer.stop()
|
||||
self.notifier.setEnabled(False)
|
||||
self.pubsub.close()
|
||||
return self.response
|
||||
|
||||
def _pubsub_readable(self, fd):
|
||||
while True:
|
||||
msg = self.pubsub.get_message()
|
||||
if msg:
|
||||
if msg["type"] == "subscribe":
|
||||
# get_message buffers, so we may already have the answer
|
||||
# let's check...
|
||||
continue
|
||||
else:
|
||||
break
|
||||
else:
|
||||
return
|
||||
channel = msg["channel"].decode()
|
||||
msg = MessageObject(topic=channel, value=MsgpackSerialization.loads(msg["data"]))
|
||||
self.connector._execute_callback(self._msg_received, msg, {})
|
||||
|
||||
|
||||
class RPCBase:
|
||||
def __init__(self, gui_id: str = None, config: dict = None, parent=None) -> None:
|
||||
self._client = BECDispatcher().client
|
||||
@@ -231,7 +269,7 @@ class RPCBase:
|
||||
parent = parent._parent
|
||||
return parent
|
||||
|
||||
def _run_rpc(self, method, *args, wait_for_rpc_response=True, **kwargs):
|
||||
def _run_rpc(self, method, *args, wait_for_rpc_response=True, timeout=3, **kwargs):
|
||||
"""
|
||||
Run the RPC call.
|
||||
|
||||
@@ -253,16 +291,24 @@ class RPCBase:
|
||||
|
||||
# pylint: disable=protected-access
|
||||
receiver = self._root._gui_id
|
||||
if wait_for_rpc_response:
|
||||
redis_msg = QtRedisMessageWaiter(
|
||||
self._client.connector, MessageEndpoints.gui_instruction_response(request_id)
|
||||
)
|
||||
|
||||
self._client.connector.set_and_publish(MessageEndpoints.gui_instructions(receiver), rpc_msg)
|
||||
|
||||
if not wait_for_rpc_response:
|
||||
return None
|
||||
response = self._wait_for_response(request_id)
|
||||
# get class name
|
||||
if not response.content["accepted"]:
|
||||
raise ValueError(response.content["message"]["error"])
|
||||
msg_result = response.content["message"].get("result")
|
||||
return self._create_widget_from_msg_result(msg_result)
|
||||
if wait_for_rpc_response:
|
||||
response = redis_msg.wait(timeout)
|
||||
|
||||
if response is None:
|
||||
raise RPCResponseTimeoutError(request_id, timeout)
|
||||
|
||||
# get class name
|
||||
if not response.accepted:
|
||||
raise ValueError(response.message["error"])
|
||||
msg_result = response.message.get("result")
|
||||
return self._create_widget_from_msg_result(msg_result)
|
||||
|
||||
def _create_widget_from_msg_result(self, msg_result):
|
||||
if msg_result is None:
|
||||
@@ -285,30 +331,6 @@ class RPCBase:
|
||||
return cls(parent=self, **msg_result)
|
||||
return msg_result
|
||||
|
||||
def _wait_for_response(self, request_id: str, timeout: int = 5):
|
||||
"""
|
||||
Wait for the response from the server.
|
||||
|
||||
Args:
|
||||
request_id(str): The request ID.
|
||||
timeout(int): The timeout in seconds.
|
||||
|
||||
Returns:
|
||||
The response from the server.
|
||||
"""
|
||||
start_time = time.time()
|
||||
response = None
|
||||
|
||||
while response is None and self.gui_is_alive() and (time.time() - start_time) < timeout:
|
||||
response = self._client.connector.get(
|
||||
MessageEndpoints.gui_instruction_response(request_id)
|
||||
)
|
||||
QCoreApplication.processEvents() # keep UI responsive (and execute signals/slots)
|
||||
if response is None and (time.time() - start_time) >= timeout:
|
||||
raise RPCResponseTimeoutError(request_id, timeout)
|
||||
|
||||
return response
|
||||
|
||||
def gui_is_alive(self):
|
||||
"""
|
||||
Check if the GUI is alive.
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
# pylint: disable=missing-module-docstring
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib
|
||||
import inspect
|
||||
import os
|
||||
import sys
|
||||
from typing import Literal
|
||||
|
||||
import black
|
||||
import isort
|
||||
from qtpy.QtWidgets import QGraphicsWidget, QWidget
|
||||
|
||||
from bec_widgets.utils import BECConnector
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
from typing import get_overloads
|
||||
@@ -14,30 +22,52 @@ else:
|
||||
"If you want to use the real function 'typing.get_overloads()', please use Python 3.11 or later."
|
||||
)
|
||||
|
||||
def get_overloads(obj):
|
||||
# Dummy function for Python versions before 3.11
|
||||
def get_overloads(_obj):
|
||||
"""
|
||||
Dummy function for Python versions before 3.11.
|
||||
"""
|
||||
return []
|
||||
|
||||
|
||||
class ClientGenerator:
|
||||
def __init__(self):
|
||||
self.header = """# This file was automatically generated by generate_cli.py\n
|
||||
from bec_widgets.cli.client_utils import rpc_call, RPCBase, BECGuiClientMixin
|
||||
from typing import Literal, Optional, overload"""
|
||||
import enum
|
||||
from typing import Literal, Optional, overload
|
||||
|
||||
from bec_widgets.cli.client_utils import RPCBase, rpc_call, BECGuiClientMixin
|
||||
|
||||
# pylint: skip-file"""
|
||||
|
||||
self.content = ""
|
||||
|
||||
def generate_client(self, published_classes: list):
|
||||
def generate_client(
|
||||
self, published_classes: dict[Literal["connector_classes", "top_level_classes"], list[type]]
|
||||
):
|
||||
"""
|
||||
Generate the client for the published classes.
|
||||
|
||||
Args:
|
||||
published_classes(list): The list of published classes (e.g. [BECWaveform1D, BECFigure]).
|
||||
published_classes(dict): A dictionary with keys "connector_classes" and "top_level_classes" and values as lists of classes.
|
||||
"""
|
||||
for cls in published_classes:
|
||||
self.write_client_enum(published_classes["top_level_classes"])
|
||||
for cls in published_classes["connector_classes"]:
|
||||
self.content += "\n\n"
|
||||
self.generate_content_for_class(cls)
|
||||
|
||||
def write_client_enum(self, published_classes: list[type]):
|
||||
"""
|
||||
Write the client enum to the content.
|
||||
"""
|
||||
self.content += """
|
||||
class Widgets(str, enum.Enum):
|
||||
\"\"\"
|
||||
Enum for the available widgets.
|
||||
\"\"\"
|
||||
"""
|
||||
for cls in published_classes:
|
||||
self.content += f'{cls.__name__} = "{cls.__name__}"\n '
|
||||
|
||||
def generate_content_for_class(self, cls):
|
||||
"""
|
||||
Generate the content for the class.
|
||||
@@ -47,11 +77,6 @@ from typing import Literal, Optional, overload"""
|
||||
"""
|
||||
|
||||
class_name = cls.__name__
|
||||
module = cls.__module__
|
||||
|
||||
# Generate the header
|
||||
# self.header += f"""
|
||||
# from {module} import {class_name}"""
|
||||
|
||||
# Generate the content
|
||||
if cls.__name__ == "BECDockArea":
|
||||
@@ -101,39 +126,85 @@ class {class_name}(RPCBase):"""
|
||||
except black.NothingChanged:
|
||||
formatted_content = full_content
|
||||
|
||||
isort.Config(
|
||||
profile="black",
|
||||
line_length=100,
|
||||
multi_line_output=3,
|
||||
include_trailing_comma=True,
|
||||
known_first_party=["bec_widgets"],
|
||||
)
|
||||
formatted_content = isort.code(formatted_content)
|
||||
|
||||
with open(file_name, "w", encoding="utf-8") as file:
|
||||
file.write(formatted_content)
|
||||
|
||||
@staticmethod
|
||||
def get_rpc_classes(
|
||||
repo_name: str,
|
||||
) -> dict[Literal["connector_classes", "top_level_classes"], list[type]]:
|
||||
"""
|
||||
Get all RPC-enabled classes in the specified repository.
|
||||
|
||||
Args:
|
||||
repo_name(str): The name of the repository.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary with keys "connector_classes" and "top_level_classes" and values as lists of classes.
|
||||
"""
|
||||
connector_classes = []
|
||||
top_level_classes = []
|
||||
anchor_module = importlib.import_module(f"{repo_name}.widgets")
|
||||
directory = os.path.dirname(anchor_module.__file__)
|
||||
for root, _, files in sorted(os.walk(directory)):
|
||||
for file in files:
|
||||
if not file.endswith(".py") or file.startswith("__"):
|
||||
continue
|
||||
|
||||
path = os.path.join(root, file)
|
||||
subs = os.path.dirname(os.path.relpath(path, directory)).split("/")
|
||||
if len(subs) == 1 and not subs[0]:
|
||||
module_name = file.split(".")[0]
|
||||
else:
|
||||
module_name = ".".join(subs + [file.split(".")[0]])
|
||||
|
||||
module = importlib.import_module(f"{repo_name}.widgets.{module_name}")
|
||||
|
||||
for name in dir(module):
|
||||
obj = getattr(module, name)
|
||||
if not hasattr(obj, "__module__") or obj.__module__ != module.__name__:
|
||||
continue
|
||||
if isinstance(obj, type) and issubclass(obj, BECConnector):
|
||||
connector_classes.append(obj)
|
||||
if len(subs) == 1 and (
|
||||
issubclass(obj, QWidget) or issubclass(obj, QGraphicsWidget)
|
||||
):
|
||||
top_level_classes.append(obj)
|
||||
|
||||
return {"connector_classes": connector_classes, "top_level_classes": top_level_classes}
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main entry point for the script, controlled by command line arguments.
|
||||
"""
|
||||
|
||||
parser = argparse.ArgumentParser(description="Auto-generate the client for RPC widgets")
|
||||
parser.add_argument("--core", action="store_true", help="Whether to generate the core client")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.core:
|
||||
current_path = os.path.dirname(__file__)
|
||||
client_path = os.path.join(current_path, "client.py")
|
||||
|
||||
rpc_classes = ClientGenerator.get_rpc_classes("bec_widgets")
|
||||
rpc_classes["connector_classes"].sort(key=lambda x: x.__name__)
|
||||
|
||||
generator = ClientGenerator()
|
||||
generator.generate_client(rpc_classes)
|
||||
generator.write(client_path)
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
import os
|
||||
|
||||
from bec_widgets.utils import BECConnector
|
||||
from bec_widgets.widgets import BECDock, BECDockArea, BECFigure, SpiralProgressBar
|
||||
from bec_widgets.widgets.figure.plots.image.image import BECImageShow
|
||||
from bec_widgets.widgets.figure.plots.image.image_item import BECImageItem
|
||||
from bec_widgets.widgets.figure.plots.motor_map.motor_map import BECMotorMap
|
||||
from bec_widgets.widgets.figure.plots.plot_base import BECPlotBase
|
||||
from bec_widgets.widgets.figure.plots.waveform.waveform import BECWaveform
|
||||
from bec_widgets.widgets.figure.plots.waveform.waveform_curve import BECCurve
|
||||
from bec_widgets.widgets.spiral_progress_bar.ring import Ring
|
||||
|
||||
current_path = os.path.dirname(__file__)
|
||||
client_path = os.path.join(current_path, "client.py")
|
||||
clss = [
|
||||
BECPlotBase,
|
||||
BECWaveform,
|
||||
BECFigure,
|
||||
BECCurve,
|
||||
BECImageShow,
|
||||
BECConnector,
|
||||
BECImageItem,
|
||||
BECMotorMap,
|
||||
BECDock,
|
||||
BECDockArea,
|
||||
SpiralProgressBar,
|
||||
Ring,
|
||||
]
|
||||
generator = ClientGenerator()
|
||||
generator.generate_client(clss)
|
||||
generator.write(client_path)
|
||||
sys.argv = ["generate_cli.py", "--core"]
|
||||
main()
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
from bec_widgets.utils import BECConnector
|
||||
from bec_widgets.widgets.figure import BECFigure
|
||||
from bec_widgets.widgets.round_status_indicator.round_status_indicator import RoundStatusIndicator
|
||||
from bec_widgets.widgets.spiral_progress_bar.spiral_progress_bar import SpiralProgressBar
|
||||
from bec_widgets.widgets.text_box.text_box import TextBox
|
||||
from bec_widgets.widgets.website.website import WebsiteWidget
|
||||
|
||||
|
||||
class RPCWidgetHandler:
|
||||
"""Handler class for creating widgets from RPC messages."""
|
||||
|
||||
widget_classes = {"BECFigure": BECFigure, "SpiralProgressBar": SpiralProgressBar}
|
||||
widget_classes = {
|
||||
"BECFigure": BECFigure,
|
||||
"SpiralProgressBar": SpiralProgressBar,
|
||||
"Website": WebsiteWidget,
|
||||
"TextBox": TextBox,
|
||||
"RoundStatusIndicator": RoundStatusIndicator,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def create_widget(widget_type, **kwargs) -> BECConnector:
|
||||
|
||||
@@ -114,7 +114,7 @@ class BECWidgetsCLIServer:
|
||||
self.client.shutdown()
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
def main():
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
@@ -166,3 +166,7 @@ if __name__ == "__main__": # pragma: no cover
|
||||
|
||||
app.aboutToQuit.connect(server.shutdown)
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
main()
|
||||
|
||||
@@ -136,17 +136,18 @@ if __name__ == "__main__": # pragma: no cover
|
||||
|
||||
module_path = os.path.dirname(bec_widgets.__file__)
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
app.setApplicationName("Jupyter Console")
|
||||
app.setApplicationDisplayName("Jupyter Console")
|
||||
qdarktheme.setup_theme("auto")
|
||||
icon = QIcon()
|
||||
icon.addFile(os.path.join(module_path, "assets", "terminal_icon.png"), size=QSize(48, 48))
|
||||
app.setWindowIcon(icon)
|
||||
|
||||
bec_dispatcher = BECDispatcher()
|
||||
client = bec_dispatcher.client
|
||||
client.start()
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
app.setApplicationName("Jupyter Console")
|
||||
app.setApplicationDisplayName("Jupyter Console")
|
||||
# qdarktheme.setup_theme("auto")
|
||||
icon = QIcon()
|
||||
icon.addFile(os.path.join(module_path, "assets", "terminal_icon.png"), size=QSize(48, 48))
|
||||
app.setWindowIcon(icon)
|
||||
win = JupyterConsoleWindow()
|
||||
win.show()
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ class ConnectionConfig(BaseModel):
|
||||
model_config: dict = {"validate_assignment": True}
|
||||
|
||||
@field_validator("gui_id")
|
||||
@classmethod
|
||||
def generate_gui_id(cls, v, values):
|
||||
"""Generate a GUI ID if none is provided."""
|
||||
if v is None:
|
||||
|
||||
@@ -9,7 +9,7 @@ import redis
|
||||
from bec_lib.client import BECClient
|
||||
from bec_lib.redis_connector import MessageObject, RedisConnector
|
||||
from bec_lib.service_config import ServiceConfig
|
||||
from qtpy.QtCore import QObject
|
||||
from qtpy.QtCore import QCoreApplication, QObject
|
||||
from qtpy.QtCore import Signal as pyqtSignal
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -71,6 +71,7 @@ class BECDispatcher:
|
||||
|
||||
_instance = None
|
||||
_initialized = False
|
||||
qapp = None
|
||||
|
||||
def __new__(cls, client=None, config: str = None, *args, **kwargs):
|
||||
if cls._instance is None:
|
||||
@@ -82,6 +83,9 @@ class BECDispatcher:
|
||||
if self._initialized:
|
||||
return
|
||||
|
||||
if not QCoreApplication.instance():
|
||||
BECDispatcher.qapp = QCoreApplication([])
|
||||
|
||||
self._slots = collections.defaultdict(set)
|
||||
self.client = client
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from .buttons import StopButton
|
||||
from .dock import BECDock, BECDockArea
|
||||
from .figure import BECFigure, FigureConfig
|
||||
from .scan_control import ScanControl
|
||||
|
||||
1
bec_widgets/widgets/buttons/__init__.py
Normal file
1
bec_widgets/widgets/buttons/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .stop_button.stop_button import StopButton
|
||||
0
bec_widgets/widgets/buttons/stop_button/__init__.py
Normal file
0
bec_widgets/widgets/buttons/stop_button/__init__.py
Normal file
32
bec_widgets/widgets/buttons/stop_button/stop_button.py
Normal file
32
bec_widgets/widgets/buttons/stop_button/stop_button.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from qtpy.QtWidgets import QPushButton
|
||||
|
||||
from bec_widgets.utils import BECConnector
|
||||
|
||||
|
||||
class StopButton(BECConnector, QPushButton):
|
||||
"""A button that stops the current scan."""
|
||||
|
||||
def __init__(self, parent=None, client=None, config=None, gui_id=None):
|
||||
super().__init__(client=client, config=config, gui_id=gui_id)
|
||||
QPushButton.__init__(self, parent=parent)
|
||||
|
||||
self.get_bec_shortcuts()
|
||||
self.setText("Stop")
|
||||
self.setStyleSheet("background-color: #cc181e; color: white")
|
||||
self.clicked.connect(self.stop_scan)
|
||||
|
||||
def stop_scan(self):
|
||||
"""Stop the scan."""
|
||||
self.queue.request_scan_abortion()
|
||||
self.queue.request_queue_reset()
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
import sys
|
||||
|
||||
from qtpy.QtWidgets import QApplication
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
widget = StopButton()
|
||||
widget.show()
|
||||
sys.exit(app.exec_())
|
||||
@@ -63,8 +63,6 @@ class BECDock(BECConnector, Dock):
|
||||
super().__init__(client=client, config=config, gui_id=gui_id)
|
||||
Dock.__init__(self, name=name, **kwargs)
|
||||
|
||||
self.parent_dock_area = parent_dock_area
|
||||
|
||||
# Layout Manager
|
||||
self.layout_manager = GridLayoutManager(self.layout)
|
||||
|
||||
@@ -73,8 +71,8 @@ class BECDock(BECConnector, Dock):
|
||||
old_area = source.area
|
||||
self.setOrientation("horizontal", force=True)
|
||||
super().dropEvent(event)
|
||||
if old_area in self.parent_dock_area.tempAreas and old_area != self.parent_dock_area:
|
||||
self.parent_dock_area.removeTempArea(old_area)
|
||||
if old_area in self.orig_area.tempAreas and old_area != self.orig_area:
|
||||
self.orig_area.removeTempArea(old_area)
|
||||
|
||||
def float(self):
|
||||
"""
|
||||
@@ -129,7 +127,7 @@ class BECDock(BECConnector, Dock):
|
||||
Args:
|
||||
title(str): The title of the dock.
|
||||
"""
|
||||
self.parent_dock_area.docks[title] = self.parent_dock_area.docks.pop(self.name())
|
||||
self.orig_area.docks[title] = self.orig_area.docks.pop(self.name())
|
||||
self.setTitle(title)
|
||||
self._name = title
|
||||
|
||||
@@ -206,7 +204,7 @@ class BECDock(BECConnector, Dock):
|
||||
"""
|
||||
Attach the dock to the parent dock area.
|
||||
"""
|
||||
self.parent_dock_area.removeTempArea(self.area)
|
||||
self.orig_area.removeTempArea(self.area)
|
||||
|
||||
def detach(self):
|
||||
"""
|
||||
@@ -231,7 +229,7 @@ class BECDock(BECConnector, Dock):
|
||||
Remove the dock from the parent dock area.
|
||||
"""
|
||||
# self.cleanup()
|
||||
self.parent_dock_area.remove_dock(self.name())
|
||||
self.orig_area.remove_dock(self.name())
|
||||
|
||||
def cleanup(self):
|
||||
"""
|
||||
|
||||
@@ -53,7 +53,6 @@ class BECWaveform(BECPlotBase):
|
||||
"set_y_lim",
|
||||
"set_grid",
|
||||
"lock_aspect_ratio",
|
||||
"plot",
|
||||
"remove",
|
||||
]
|
||||
scan_signal_update = pyqtSignal()
|
||||
|
||||
@@ -68,7 +68,7 @@ class BECCurve(BECConnector, pg.PlotDataItem):
|
||||
"set",
|
||||
"set_data",
|
||||
"set_color",
|
||||
"set_colormap_z",
|
||||
"set_color_map_z",
|
||||
"set_symbol",
|
||||
"set_symbol_color",
|
||||
"set_symbol_size",
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
from enum import Enum
|
||||
from typing import Literal
|
||||
|
||||
from PyQt6.QtGui import QPaintEvent
|
||||
from qtpy import QtGui
|
||||
from qtpy.QtCore import QRect, Qt
|
||||
from qtpy.QtWidgets import QApplication, QMainWindow, QWidget
|
||||
|
||||
from bec_widgets.utils.bec_connector import BECConnector, ConnectionConfig
|
||||
|
||||
|
||||
class RoundStatusIndicatorConfig(ConnectionConfig):
|
||||
"""
|
||||
Configuration for the RoundStatusIndicator
|
||||
"""
|
||||
|
||||
state: Literal["success", "failure", "warning"] = "success"
|
||||
|
||||
|
||||
class RoundStatusIndicator(BECConnector, QWidget):
|
||||
|
||||
indicator_config = {
|
||||
"success": {"color": "#24a148", "text": "✔", "offset": 0.25},
|
||||
"failure": {"color": "#da1e28", "text": "✘", "offset": 0.28},
|
||||
"warning": {"color": "#ffcc00", "text": "!", "offset": 0.28},
|
||||
}
|
||||
|
||||
USER_ACCESS = ["set_state"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
client=None,
|
||||
config: RoundStatusIndicatorConfig | dict | None = None,
|
||||
gui_id=None,
|
||||
parent=None,
|
||||
):
|
||||
super().__init__(client=client, config=config, gui_id=gui_id)
|
||||
QWidget.__init__(self, parent=parent)
|
||||
self.config = config or RoundStatusIndicatorConfig(widget_class=self.__class__.__name__)
|
||||
self.active_state_config = self.indicator_config[self.config.state]
|
||||
|
||||
def paintEvent(self, _event: QPaintEvent) -> None:
|
||||
"""
|
||||
Paint the widget.
|
||||
|
||||
Args:
|
||||
_event (QPaintEvent): The paint event
|
||||
"""
|
||||
|
||||
color = QtGui.QColor(self.active_state_config["color"]) # Red color as default
|
||||
text_color = QtGui.QColor("#ffffff") # White color for text
|
||||
text = self.active_state_config["text"]
|
||||
offset = self.active_state_config["offset"]
|
||||
|
||||
painter = QtGui.QPainter(self)
|
||||
painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing)
|
||||
|
||||
# Determine the size of the widget
|
||||
size = min(self.width(), self.height())
|
||||
rect = QRect(0, 0, size, size)
|
||||
|
||||
# Set the color and draw the disk
|
||||
painter.setBrush(color)
|
||||
painter.setPen(Qt.PenStyle.NoPen)
|
||||
painter.drawEllipse(rect)
|
||||
|
||||
# Draw the exclamation mark "!"
|
||||
painter.setPen(text_color)
|
||||
font = painter.font()
|
||||
font.setPixelSize(int(size * 0.9)) # Adjust font size based on widget size
|
||||
painter.setFont(font)
|
||||
|
||||
# text_rect = painter.boundingRect(rect, Qt.AlignmentFlag.AlignCenter, text)
|
||||
|
||||
font_metrics = QtGui.QFontMetrics(font)
|
||||
text_width = font_metrics.horizontalAdvance(text)
|
||||
text_height = font_metrics.height()
|
||||
|
||||
text_x = int(rect.center().x() - text_width / 2)
|
||||
text_y = int(rect.center().y() + text_height * offset)
|
||||
painter.drawText(text_x, text_y, text)
|
||||
|
||||
def set_state(self, state: Literal["success", "failure", "warning"]) -> None:
|
||||
"""
|
||||
Set the state of the indicator.
|
||||
|
||||
Args:
|
||||
state (str): The state of the indicator. Can be "success", "failure", or "warning"
|
||||
|
||||
"""
|
||||
self.config.state = state
|
||||
self.active_state_config = self.indicator_config[state]
|
||||
self.update()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication([])
|
||||
|
||||
window = QMainWindow()
|
||||
status_indicator = RoundStatusIndicator()
|
||||
window.setCentralWidget(status_indicator)
|
||||
window.resize(200, 200)
|
||||
window.show()
|
||||
|
||||
# Example of changing the color
|
||||
status_indicator.set_state("warning") # Change to a different color
|
||||
|
||||
app.exec()
|
||||
0
bec_widgets/widgets/text_box/__init__.py
Normal file
0
bec_widgets/widgets/text_box/__init__.py
Normal file
127
bec_widgets/widgets/text_box/text_box.py
Normal file
127
bec_widgets/widgets/text_box/text_box.py
Normal file
@@ -0,0 +1,127 @@
|
||||
import re
|
||||
|
||||
from pydantic import Field, field_validator
|
||||
from qtpy.QtWidgets import QTextEdit
|
||||
|
||||
from bec_widgets.utils.bec_connector import BECConnector, ConnectionConfig
|
||||
from bec_widgets.utils.colors import Colors
|
||||
|
||||
|
||||
class TextBoxConfig(ConnectionConfig):
|
||||
|
||||
theme: str = Field("dark", description="The theme of the figure widget.")
|
||||
font_color: str = Field("#FFF", description="The font color of the text")
|
||||
background_color: str = Field("#000", description="The background color of the widget.")
|
||||
font_size: int = Field(16, description="The font size of the text in the widget.")
|
||||
text: str = Field("", description="The text to display in the widget.")
|
||||
|
||||
@classmethod
|
||||
@field_validator("theme")
|
||||
def validate_theme(cls, v):
|
||||
"""Validate the theme of the figure widget."""
|
||||
if v not in ["dark", "light"]:
|
||||
raise ValueError("Theme must be either 'dark' or 'light'")
|
||||
return v
|
||||
|
||||
_validate_font_color = field_validator("font_color")(Colors.validate_color)
|
||||
_validate_background_color = field_validator("background_color")(Colors.validate_color)
|
||||
|
||||
|
||||
class TextBox(BECConnector, QTextEdit):
|
||||
|
||||
USER_ACCESS = ["set_color", "set_text", "set_font_size"]
|
||||
|
||||
def __init__(self, text: str = "", parent=None, client=None, config=None, gui_id=None):
|
||||
if config is None:
|
||||
config = TextBoxConfig(widget_class=self.__class__.__name__)
|
||||
else:
|
||||
if isinstance(config, dict):
|
||||
config = TextBoxConfig(**config)
|
||||
self.config = config
|
||||
super().__init__(client=client, config=config, gui_id=gui_id)
|
||||
QTextEdit.__init__(self, parent=parent)
|
||||
|
||||
self.config = config
|
||||
self.setReadOnly(True)
|
||||
self.setGeometry(self.rect())
|
||||
self.set_color(self.config.background_color, self.config.font_color)
|
||||
if not text:
|
||||
text = "<h1>Welcome to the BEC Widget TextBox</h1><p>A widget that allows user to display text in plain and HTML format.</p><p>This is an example of displaying HTML text.</p>"
|
||||
self.set_text(text)
|
||||
|
||||
def change_theme(self) -> None:
|
||||
"""
|
||||
Change the theme of the figure widget.
|
||||
"""
|
||||
if self.config.theme == "dark":
|
||||
theme = "light"
|
||||
font_color = "#000"
|
||||
background_color = "#FFF"
|
||||
else:
|
||||
theme = "dark"
|
||||
font_color = "#FFF"
|
||||
background_color = "#000"
|
||||
self.config.theme = theme
|
||||
self.set_color(background_color, font_color)
|
||||
|
||||
def set_color(self, background_color: str, font_color: str) -> None:
|
||||
"""Set the background color of the widget.
|
||||
|
||||
Args:
|
||||
background_color (str): The color to set the background in HEX.
|
||||
font_color (str): The color to set the font in HEX.
|
||||
|
||||
"""
|
||||
self.config.background_color = background_color
|
||||
self.config.font_color = font_color
|
||||
self._update_stylesheet()
|
||||
|
||||
def set_font_size(self, size: int) -> None:
|
||||
"""Set the font size of the text in the widget.
|
||||
|
||||
Args:
|
||||
size (int): The font size to set.
|
||||
"""
|
||||
self.config.font_size = size
|
||||
self._update_stylesheet()
|
||||
|
||||
def _update_stylesheet(self):
|
||||
"""Update the stylesheet of the widget."""
|
||||
self.setStyleSheet(
|
||||
f"background-color: {self.config.background_color}; color: {self.config.font_color}; font-size: {self.config.font_size}px"
|
||||
)
|
||||
|
||||
def set_text(self, text: str) -> None:
|
||||
"""Set the text of the widget.
|
||||
|
||||
Args:
|
||||
text (str): The text to set.
|
||||
"""
|
||||
if self.is_html(text):
|
||||
self.setHtml(text)
|
||||
else:
|
||||
self.setPlainText(text)
|
||||
self.config.text = text
|
||||
|
||||
def is_html(self, text: str) -> bool:
|
||||
"""Check if the text contains HTML tags.
|
||||
|
||||
Args:
|
||||
text (str): The text to check.
|
||||
|
||||
Returns:
|
||||
bool: True if the text contains HTML tags, False otherwise.
|
||||
"""
|
||||
return bool(re.search(r"<[a-zA-Z/][^>]*>", text))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
from qtpy.QtWidgets import QApplication
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
widget = TextBox()
|
||||
widget.show()
|
||||
sys.exit(app.exec())
|
||||
0
bec_widgets/widgets/website/__init__.py
Normal file
0
bec_widgets/widgets/website/__init__.py
Normal file
65
bec_widgets/widgets/website/website.py
Normal file
65
bec_widgets/widgets/website/website.py
Normal file
@@ -0,0 +1,65 @@
|
||||
from qtpy.QtCore import QUrl
|
||||
from qtpy.QtWebEngineWidgets import QWebEngineView
|
||||
from qtpy.QtWidgets import QApplication
|
||||
|
||||
from bec_widgets.utils import BECConnector
|
||||
|
||||
|
||||
class WebsiteWidget(BECConnector, QWebEngineView):
|
||||
"""
|
||||
A simple widget to display a website
|
||||
"""
|
||||
|
||||
USER_ACCESS = ["set_url", "get_url", "reload", "back", "forward"]
|
||||
|
||||
def __init__(self, url: str = None, parent=None, config=None, client=None, gui_id=None):
|
||||
super().__init__(client=client, config=config, gui_id=gui_id)
|
||||
QWebEngineView.__init__(self, parent=parent)
|
||||
self.set_url(url)
|
||||
|
||||
def set_url(self, url: str) -> None:
|
||||
"""
|
||||
Set the url of the website widget
|
||||
|
||||
Args:
|
||||
url (str): The url to set
|
||||
"""
|
||||
if not url:
|
||||
return
|
||||
self.setUrl(QUrl(url))
|
||||
|
||||
def get_url(self) -> str:
|
||||
"""
|
||||
Get the current url of the website widget
|
||||
|
||||
Returns:
|
||||
str: The current url
|
||||
"""
|
||||
return self.url().toString()
|
||||
|
||||
def reload(self):
|
||||
"""
|
||||
Reload the website
|
||||
"""
|
||||
QWebEngineView.reload(self)
|
||||
|
||||
def back(self):
|
||||
"""
|
||||
Go back in the history
|
||||
"""
|
||||
QWebEngineView.back(self)
|
||||
|
||||
def forward(self):
|
||||
"""
|
||||
Go forward in the history
|
||||
"""
|
||||
QWebEngineView.forward(self)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
mainWin = WebsiteWidget("https://scilog.psi.ch")
|
||||
mainWin.show()
|
||||
sys.exit(app.exec())
|
||||
@@ -1,18 +1,46 @@
|
||||
(developer)=
|
||||
# Development
|
||||
# Developer
|
||||
|
||||
To contribute to the development of BEC Widgets, start by setting up the development environment:
|
||||
Welcome to the BEC Widgets developer guide! This section is intended for developers who want to contribute to the development of BEC Widgets.
|
||||
|
||||
1. **Clone the Repository**:
|
||||
```bash
|
||||
git clone https://gitlab.psi.ch/bec/bec_widgets
|
||||
cd bec_widgets
|
||||
```
|
||||
2. **Install in Editable Mode**:
|
||||
```{toctree}
|
||||
---
|
||||
maxdepth: 2
|
||||
hidden: true
|
||||
---
|
||||
|
||||
Installing the package in editable mode allows you to make changes to the code and test them in real-time.
|
||||
```bash
|
||||
pip install -e .[dev,pyqt6]
|
||||
getting_started/getting_started.md
|
||||
widgets/widgets.md
|
||||
api_reference/api_reference.md
|
||||
```
|
||||
|
||||
|
||||
***
|
||||
|
||||
````{grid} 2
|
||||
:gutter: 5
|
||||
|
||||
```{grid-item-card}
|
||||
:link: developer.getting_started
|
||||
:link-type: ref
|
||||
:img-top: /assets/rocket_launch_48dp.svg
|
||||
:text-align: center
|
||||
|
||||
## Getting Started
|
||||
|
||||
Learn how to install BEC Widgets and get started with the framework.
|
||||
```
|
||||
|
||||
```{grid-item-card}
|
||||
:link: developer.widgets
|
||||
:link-type: ref
|
||||
:img-top: /assets/apps_48dp.svg
|
||||
:text-align: center
|
||||
|
||||
## Widgets
|
||||
|
||||
Learn about the building blocks of larger applications: widgets.
|
||||
```
|
||||
````
|
||||
|
||||
|
||||
|
||||
27
docs/developer/getting_started/development.md
Normal file
27
docs/developer/getting_started/development.md
Normal file
@@ -0,0 +1,27 @@
|
||||
(developer.development)=
|
||||
# Development
|
||||
|
||||
If you like to contribute to the development of BEC Widgets, you can follow the steps below to set up your development environment.
|
||||
BEC Widgets works in conjunction with [BEC](https://bec.readthedocs.io/en/latest/).
|
||||
Therefore, we recommend that you install BEC first following the [developer instructions](https://bec.readthedocs.io/en/latest/developer/getting_started/install_developer_env.html) and include BEC Widgets.
|
||||
|
||||
If you already have a BEC environment set up, you can install BEC Widgets in editable mode into your BEC Python environment.
|
||||
|
||||
**Prerequisites**
|
||||
1. **Python Version:** BEC Widgets requires Python version 3.10 or higher. Verify your Python version to ensure compatibility.
|
||||
2. **BEC Installation:** BEC Widgets works in conjunction with BEC. While BEC is a dependency and will be installed automatically, you can find more information about BEC and its installation process in the [BEC documentation](https://beamline-experiment-control.readthedocs.io/en/latest/).
|
||||
|
||||
**Clone the Repository**:
|
||||
```bash
|
||||
git clone https://gitlab.psi.ch/bec/bec_widgets
|
||||
cd bec_widgets
|
||||
```
|
||||
**Install in Editable Mode**:
|
||||
|
||||
Please install the package in editable mode into your BEC Python environemnt.
|
||||
```bash
|
||||
pip install -e '.[dev,pyqt6]'
|
||||
```
|
||||
This installs the package together with [PyQT6](https://www.riverbankcomputing.com/static/Docs/PyQt6/introduction.html).
|
||||
|
||||
|
||||
12
docs/developer/getting_started/getting_started.md
Normal file
12
docs/developer/getting_started/getting_started.md
Normal file
@@ -0,0 +1,12 @@
|
||||
(developer.getting_started)=
|
||||
# Getting Started
|
||||
This section provides valuable information for developers who want to contribute to the development of BEC Widgets. The guide will help you set up the development environment, understand the modular development concept of BEC Widgets, and contribute to the project.
|
||||
|
||||
```{toctree}
|
||||
---
|
||||
maxdepth: 2
|
||||
hidden: false
|
||||
---
|
||||
|
||||
development/
|
||||
```
|
||||
12
docs/developer/widgets/widgets.md
Normal file
12
docs/developer/widgets/widgets.md
Normal file
@@ -0,0 +1,12 @@
|
||||
(developer.widgets)=
|
||||
# Widgets
|
||||
This section provides an introduction to the building blocks of BEC Widgets: widgets. Widgets are the basic components of the graphical user interface (GUI) and are used to create larger applications. We will cover key topics such as how to develop new widgets or how to customise existing widgets. For details on the already available widgets and their usage, please refer to user section about [widgets](#user.widgets)
|
||||
|
||||
```{toctree}
|
||||
---
|
||||
maxdepth: 2
|
||||
hidden: false
|
||||
---
|
||||
|
||||
how_to_develop_a_widget/
|
||||
```
|
||||
@@ -1,8 +1,118 @@
|
||||
(user.customisation)=
|
||||
# Customisation
|
||||
|
||||
BEC Widgets are designed to be used with QtDesigner to quicly design GUI.
|
||||
## Leveraging BEC Widgets in custom GUI applications
|
||||
|
||||
BEC Widgets can be used to compose a complete Qt graphical application, along with
|
||||
other QWidgets. The only requirement is to connect to BEC servers in order to get
|
||||
data, or to interact with BEC components. This role is devoted to the BECDispatcher,
|
||||
a singleton object which has to be instantiated **after the QApplication is created**.
|
||||
|
||||
A typical BEC Widgets custom application "main" entry point should follow the template
|
||||
below:
|
||||
|
||||
```
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
from bec_widgets.utils.bec_dispatcher import BECDispatcher
|
||||
from qtpy.QtWidgets import QApplication
|
||||
|
||||
# optional command line arguments processing
|
||||
parser = argparse.ArgumentParser(description="...")
|
||||
parser.add_argument( ...)
|
||||
...
|
||||
args = parser.parse_args()
|
||||
|
||||
# creation of the Qt application
|
||||
app = QApplication([])
|
||||
|
||||
# creation of BEC Dispatcher
|
||||
# /!\ important: after the QApplication has been instantiated
|
||||
bec_dispatcher = BECDispatcher()
|
||||
client = bec_dispatcher.client
|
||||
client.start()
|
||||
|
||||
# (optional) processing of command line args,
|
||||
# creation of a main window depending on the command line arguments (or not)
|
||||
if args.xxx == "...":
|
||||
window = ...
|
||||
|
||||
# display of the main window and start of Qt event loop
|
||||
window.show()
|
||||
sys.exit(app.exec())
|
||||
```
|
||||
|
||||
The main "window" object presents the layout of widgets to the user and allows
|
||||
users to interact. BEC Widgets must be placed in the window:
|
||||
|
||||
```
|
||||
from qtpy.QWidgets import QMainWindow
|
||||
from bec_widgets.widgets import BECFigure
|
||||
|
||||
window = QMainWindow()
|
||||
bec_figure = BECFigure(gui_id="my_gui_app_id")
|
||||
window.setCentralWidget(bec_figure)
|
||||
|
||||
# prepare to plot samx motor vs bpm4i value
|
||||
bec_figure.plot(x_name="samx", y_name="bpm4i")
|
||||
```
|
||||
|
||||
In the example just above, the resulting application will show a plot of samx
|
||||
positions on the horizontal axis, and beam intensity on the vertical axis
|
||||
(when the next scan will be started).
|
||||
|
||||
It is important to ensure proper cleanup of the resources is done when application
|
||||
quits:
|
||||
|
||||
```
|
||||
def final_cleanup():
|
||||
bec_figure.clear_all()
|
||||
bec_figure.client.shutdown()
|
||||
|
||||
window.aboutToQuit.connect(final_cleanup)
|
||||
```
|
||||
|
||||
Final example:
|
||||
|
||||
```
|
||||
import sys
|
||||
from qtpy.QtWidgets import QMainWindow, QApplication
|
||||
from bec_widgets.widgets import BECFigure
|
||||
from bec_widgets.utils.bec_dispatcher import BECDispatcher
|
||||
|
||||
# creation of the Qt application
|
||||
app = QApplication([])
|
||||
|
||||
# creation of BEC Dispatcher
|
||||
bec_dispatcher = BECDispatcher()
|
||||
client = bec_dispatcher.client
|
||||
client.start()
|
||||
|
||||
# creation of main window
|
||||
window = QMainWindow()
|
||||
|
||||
# inserting BEC Widgets
|
||||
bec_figure = BECFigure(parent=window, gui_id="my_gui_app_id")
|
||||
window.setCentralWidget(bec_figure)
|
||||
|
||||
bec_figure.plot(x_name="samx", y_name="bpm4i")
|
||||
|
||||
# ensuring proper cleanup
|
||||
def final_cleanup():
|
||||
bec_figure.clear_all()
|
||||
bec_figure.client.shutdown()
|
||||
|
||||
app.aboutToQuit.connect(final_cleanup)
|
||||
|
||||
# execution
|
||||
window.show()
|
||||
sys.exit(app.exec())
|
||||
```
|
||||
|
||||
## Writing applications using Qt Designer
|
||||
|
||||
BEC Widgets are designed to be used with QtDesigner to quickly design GUI.
|
||||
|
||||
## Example of promoting widgets in Qt Designer
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ Before installing BEC Widgets, please ensure the following requirements are met:
|
||||
|
||||
**Standard Installation**
|
||||
|
||||
To install BEC Widgets using the pip package manager, execute the following command in your terminal for getting the default PyQT6 version in your python environment:
|
||||
To install BEC Widgets using the pip package manager, execute the following command in your terminal for getting the default PyQT6 version into your python environment for BEC:
|
||||
|
||||
|
||||
```bash
|
||||
|
||||
38
docs/user/widgets/buttons.md
Normal file
38
docs/user/widgets/buttons.md
Normal file
@@ -0,0 +1,38 @@
|
||||
(user.widgets.buttons)=
|
||||
|
||||
# Buttons Widgets
|
||||
|
||||
This section consolidates various custom buttons used within the BEC GUIs, facilitating the integration of these
|
||||
controls into different layouts.
|
||||
|
||||
## Stop Button
|
||||
|
||||
**Purpose:**
|
||||
|
||||
The `Stop Button` provides a user interface control to immediately halt the execution of the current operation in the
|
||||
BEC Client. It is designed for easy integration into any BEC GUI layout.
|
||||
|
||||
**Key Features:**
|
||||
|
||||
- **Immediate Termination:** Halts the execution of the current script or process.
|
||||
- **Queue Management:** Clears any pending operations in the scan queue, ensuring the system is ready for new tasks.
|
||||
|
||||
**Code example:**
|
||||
|
||||
Integrating the `StopButton` into a BEC GUI layout is straightforward. The following example demonstrates how to embed
|
||||
a `StopButton` within a GUI layout:
|
||||
|
||||
```python
|
||||
from qtpy.QtWidgets import QWidget, QVBoxLayout
|
||||
from bec_widgets.widgets import StopButton
|
||||
|
||||
|
||||
class MyGui(QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setLayout(QVBoxLayout(self)) # Initialize the layout for the widget
|
||||
|
||||
# Create and add the StopButton to the layout
|
||||
self.stop_button = StopButton()
|
||||
self.layout().addWidget(self.stop_button)
|
||||
```
|
||||
33
docs/user/widgets/text_box.md
Normal file
33
docs/user/widgets/text_box.md
Normal file
@@ -0,0 +1,33 @@
|
||||
(user.widgets.text_box)=
|
||||
# [Text Box Widget](/api_reference/_autosummary/bec_widgets.cli.client.TextBox)
|
||||
**Purpose:**
|
||||
|
||||
The Text Box Widget is a widget that allows you to display text within the BEC GUI. The widget can be used to display plain text or HTML text.
|
||||
|
||||
**Key Features:**
|
||||
|
||||
- set the text to display.
|
||||
- automatically detects if the text is plain text or HTML text.
|
||||
- set background color and font color.
|
||||
|
||||
**Code example:**
|
||||
|
||||
The following code snipped demonstrates how to create a `TextBox` widget using BEC Widgets within BEC.
|
||||
```python
|
||||
text_box = gui.add_dock().add_widget("TextBox")
|
||||
# set the text to display
|
||||
text_box.set_text("Hello, World!")
|
||||
# set the background color and font color
|
||||
text_box.set_color(backgroud_color="#FFF", font_color="#000")
|
||||
# set the text to display as HTML
|
||||
text_box.set_text("<h1>Welcome to BEC Widgets</h1><p>This is an example of displaying <strong>HTML</strong> text.</p>")
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
21
docs/user/widgets/website.md
Normal file
21
docs/user/widgets/website.md
Normal file
@@ -0,0 +1,21 @@
|
||||
(user.widgets.website)=
|
||||
# [Website Widget](/api_reference/_autosummary/bec_widgets.cli.client.WebsiteWidget)
|
||||
**Purpose:**
|
||||
|
||||
The Website Widget is a widget that allows you to display a website within the BEC GUI. The widget can be used to display any website.
|
||||
|
||||
**Key Features:**
|
||||
|
||||
- set the URL of the website to display.
|
||||
- reload the website.
|
||||
- navigate back and forward in the website history.
|
||||
|
||||
**Code example:**
|
||||
|
||||
The following code snipped demonstrates how to create a `WebsiteWidget` using BEC Widgets within BEC.
|
||||
```python
|
||||
# adds a new dock with a website widget
|
||||
web = gui.add_dock().add_widget("Website")
|
||||
# set the URL of the website to display
|
||||
web.set_url("https://bec.readthedocs.io/en/latest/")
|
||||
```
|
||||
@@ -10,6 +10,9 @@ hidden: false
|
||||
|
||||
bec_figure/
|
||||
spiral_progress_bar/
|
||||
website/
|
||||
buttons/
|
||||
text_box/
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "bec_widgets"
|
||||
version = "0.58.1"
|
||||
version = "0.63.2"
|
||||
description = "BEC Widgets"
|
||||
requires-python = ">=3.10"
|
||||
classifiers = [
|
||||
@@ -19,10 +19,12 @@ dependencies = [
|
||||
"qtpy",
|
||||
"pyqtgraph",
|
||||
"bec_lib",
|
||||
"bec_ipython_client", # needed for jupyter widget
|
||||
"zmq",
|
||||
"h5py",
|
||||
"pyqtdarktheme",
|
||||
"black",
|
||||
"black", # needed for bw-generate-cli
|
||||
"isort", # needed for bw-generate-cli
|
||||
]
|
||||
|
||||
|
||||
@@ -34,17 +36,20 @@ dev = [
|
||||
"pytest-xvfb",
|
||||
"coverage",
|
||||
"pytest-qt",
|
||||
"isort",
|
||||
"fakeredis",
|
||||
]
|
||||
pyqt5 = ["PyQt5>=5.9"]
|
||||
pyqt6 = ["PyQt6>=6.7"]
|
||||
pyqt5 = ["PyQt5>=5.9", "PyQtWebEngine>=5.9"]
|
||||
pyqt6 = ["PyQt6>=6.7", "PyQt6-WebEngine>=6.7"]
|
||||
pyside6 = ["PySide6>=6.7"]
|
||||
|
||||
[project.urls]
|
||||
"Bug Tracker" = "https://gitlab.psi.ch/bec/bec_widgets/issues"
|
||||
Homepage = "https://gitlab.psi.ch/bec/bec_widgets"
|
||||
|
||||
[project.scripts]
|
||||
bw-generate-cli = "bec_widgets.cli.generate_cli:main"
|
||||
bec-gui-server = "bec_widgets.cli.server:main"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
include = ["*"]
|
||||
|
||||
@@ -57,6 +62,7 @@ profile = "black"
|
||||
line_length = 100
|
||||
multi_line_output = 3
|
||||
include_trailing_comma = true
|
||||
known_first_party = ["bec_widgets"]
|
||||
|
||||
[tool.semantic_release]
|
||||
build_command = "python -m build"
|
||||
|
||||
@@ -253,8 +253,14 @@ def test_auto_update(bec_client_lib, rpc_server_dock):
|
||||
plt_data = widgets[0].get_all_data()
|
||||
|
||||
# check plotted data
|
||||
assert plt_data["bpm4i-bpm4i"]["x"] == last_scan_data["samx"]["samx"].val
|
||||
assert plt_data["bpm4i-bpm4i"]["y"] == last_scan_data["bpm4i"]["bpm4i"].val
|
||||
assert (
|
||||
plt_data[f"Scan {status.scan.scan_number} - bpm4i"]["x"]
|
||||
== last_scan_data["samx"]["samx"].val
|
||||
)
|
||||
assert (
|
||||
plt_data[f"Scan {status.scan.scan_number} - bpm4i"]["y"]
|
||||
== last_scan_data["bpm4i"]["bpm4i"].val
|
||||
)
|
||||
|
||||
status = scans.grid_scan(
|
||||
dev.samx, -10, 10, 5, dev.samy, -5, 5, 5, exp_time=0.05, relative=False
|
||||
@@ -268,5 +274,11 @@ def test_auto_update(bec_client_lib, rpc_server_dock):
|
||||
last_scan_data = queue.scan_storage.storage[-1].data
|
||||
|
||||
# check plotted data
|
||||
assert plt_data[f"Scan {status.scan.scan_number}"]["x"] == last_scan_data["samx"]["samx"].val
|
||||
assert plt_data[f"Scan {status.scan.scan_number}"]["y"] == last_scan_data["samy"]["samy"].val
|
||||
assert (
|
||||
plt_data[f"Scan {status.scan.scan_number} - {dock.selected_device}"]["x"]
|
||||
== last_scan_data["samx"]["samx"].val
|
||||
)
|
||||
assert (
|
||||
plt_data[f"Scan {status.scan.scan_number} - {dock.selected_device}"]["y"]
|
||||
== last_scan_data["samy"]["samy"].val
|
||||
)
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from textwrap import dedent
|
||||
|
||||
import black
|
||||
import pytest
|
||||
import isort
|
||||
|
||||
from bec_widgets.cli.generate_cli import ClientGenerator
|
||||
|
||||
# pylint: disable=missing-function-docstring
|
||||
|
||||
|
||||
# Mock classes to test the generator
|
||||
class MockBECWaveform1D:
|
||||
@@ -24,25 +26,39 @@ class MockBECFigure:
|
||||
|
||||
def add_plot(self, plot_id: str):
|
||||
"""Add a plot to the figure."""
|
||||
pass
|
||||
|
||||
def remove_plot(self, plot_id: str):
|
||||
"""Remove a plot from the figure."""
|
||||
pass
|
||||
|
||||
|
||||
def test_client_generator_with_black_formatting():
|
||||
generator = ClientGenerator()
|
||||
generator.generate_client([MockBECWaveform1D, MockBECFigure])
|
||||
rpc_classes = {
|
||||
"connector_classes": [MockBECWaveform1D, MockBECFigure],
|
||||
"top_level_classes": [MockBECFigure],
|
||||
}
|
||||
generator.generate_client(rpc_classes)
|
||||
|
||||
# Format the expected output with black to ensure it matches the generator output
|
||||
expected_output = dedent(
|
||||
'''\
|
||||
# This file was automatically generated by generate_cli.py
|
||||
|
||||
from bec_widgets.cli.client_utils import rpc_call, RPCBase, BECGuiClientMixin
|
||||
import enum
|
||||
from typing import Literal, Optional, overload
|
||||
|
||||
from bec_widgets.cli.client_utils import BECGuiClientMixin, RPCBase, rpc_call
|
||||
|
||||
# pylint: skip-file
|
||||
|
||||
class Widgets(str, enum.Enum):
|
||||
"""
|
||||
Enum for the available widgets.
|
||||
"""
|
||||
|
||||
MockBECFigure = "MockBECFigure"
|
||||
|
||||
|
||||
class MockBECWaveform1D(RPCBase):
|
||||
@rpc_call
|
||||
def set_frequency(self, frequency: float) -> list:
|
||||
@@ -78,4 +94,20 @@ def test_client_generator_with_black_formatting():
|
||||
generator.header + "\n" + generator.content, mode=black.FileMode(line_length=100)
|
||||
)
|
||||
|
||||
generated_output_formatted = isort.code(generated_output_formatted)
|
||||
|
||||
assert expected_output_formatted == generated_output_formatted
|
||||
|
||||
|
||||
def test_client_generator_classes():
|
||||
generator = ClientGenerator()
|
||||
out = generator.get_rpc_classes("bec_widgets")
|
||||
assert list(out.keys()) == ["connector_classes", "top_level_classes"]
|
||||
connector_cls_names = [cls.__name__ for cls in out["connector_classes"]]
|
||||
top_level_cls_names = [cls.__name__ for cls in out["top_level_classes"]]
|
||||
|
||||
assert "BECFigure" in connector_cls_names
|
||||
assert "BECWaveform" in connector_cls_names
|
||||
assert "BECDockArea" in top_level_cls_names
|
||||
assert "BECFigure" in top_level_cls_names
|
||||
assert "BECWaveform" not in top_level_cls_names
|
||||
|
||||
25
tests/unit_tests/test_stop_button.py
Normal file
25
tests/unit_tests/test_stop_button.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# pylint: disable=missing-function-docstring, missing-module-docstring, unused-import
|
||||
|
||||
import pytest
|
||||
|
||||
from bec_widgets.widgets import StopButton
|
||||
|
||||
from .client_mocks import mocked_client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def stop_button(qtbot, mocked_client):
|
||||
widget = StopButton(client=mocked_client)
|
||||
qtbot.addWidget(widget)
|
||||
qtbot.waitExposed(widget)
|
||||
yield widget
|
||||
widget.close()
|
||||
|
||||
|
||||
def test_stop_button(stop_button):
|
||||
assert stop_button.text() == "Stop"
|
||||
assert stop_button.styleSheet() == "background-color: #cc181e; color: white"
|
||||
stop_button.click()
|
||||
assert stop_button.queue.request_scan_abortion.called
|
||||
assert stop_button.queue.request_queue_reset.called
|
||||
stop_button.close()
|
||||
55
tests/unit_tests/test_text_box_widget.py
Normal file
55
tests/unit_tests/test_text_box_widget.py
Normal file
@@ -0,0 +1,55 @@
|
||||
import re
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from bec_widgets.widgets.text_box.text_box import TextBox
|
||||
|
||||
from .client_mocks import mocked_client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def text_box_widget(qtbot, mocked_client):
|
||||
widget = TextBox(client=mocked_client)
|
||||
qtbot.addWidget(widget)
|
||||
qtbot.waitExposed(widget)
|
||||
yield widget
|
||||
widget.close()
|
||||
|
||||
|
||||
def test_textbox_widget(text_box_widget):
|
||||
"""Test the TextBox widget."""
|
||||
text = "Hello World!"
|
||||
text_box_widget.set_text(text)
|
||||
assert text_box_widget.toPlainText() == text
|
||||
|
||||
text_box_widget.set_color("#FFDDC1", "#123456")
|
||||
text_box_widget.set_font_size(20)
|
||||
assert (
|
||||
text_box_widget.styleSheet() == "background-color: #FFDDC1; color: #123456; font-size: 20px"
|
||||
)
|
||||
text_box_widget.set_color("white", "blue")
|
||||
text_box_widget.set_font_size(14)
|
||||
assert text_box_widget.styleSheet() == "background-color: white; color: blue; font-size: 14px"
|
||||
text = "<h1>Welcome to PyQt6</h1><p>This is an example of displaying <strong>HTML</strong> text.</p>"
|
||||
with mock.patch.object(text_box_widget, "setHtml") as mocked_set_html:
|
||||
text_box_widget.set_text(text)
|
||||
assert mocked_set_html.call_count == 1
|
||||
assert mocked_set_html.call_args == mock.call(text)
|
||||
|
||||
|
||||
def test_textbox_change_theme(text_box_widget):
|
||||
"""Test change theme functionaility"""
|
||||
# Default is dark theme
|
||||
text_box_widget.change_theme()
|
||||
assert text_box_widget.config.theme == "light"
|
||||
assert (
|
||||
text_box_widget.styleSheet()
|
||||
== f"background-color: #FFF; color: #000; font-size: {text_box_widget.config.font_size}px"
|
||||
)
|
||||
text_box_widget.change_theme()
|
||||
assert text_box_widget.config.theme == "dark"
|
||||
assert (
|
||||
text_box_widget.styleSheet()
|
||||
== f"background-color: #000; color: #FFF; font-size: {text_box_widget.config.font_size}px"
|
||||
)
|
||||
27
tests/unit_tests/test_website_widget.py
Normal file
27
tests/unit_tests/test_website_widget.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import pytest
|
||||
from qtpy.QtCore import QUrl
|
||||
|
||||
from bec_widgets.widgets.website.website import WebsiteWidget
|
||||
|
||||
from .client_mocks import mocked_client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def website_widget(qtbot, mocked_client):
|
||||
widget = WebsiteWidget(client=mocked_client)
|
||||
qtbot.addWidget(widget)
|
||||
qtbot.waitExposed(widget)
|
||||
yield widget
|
||||
widget.page().deleteLater()
|
||||
qtbot.wait(1000)
|
||||
|
||||
|
||||
def test_website_widget_set_url(website_widget):
|
||||
website_widget.set_url("https://scilog.psi.ch")
|
||||
assert website_widget.url() == QUrl("https://scilog.psi.ch")
|
||||
|
||||
website_widget.set_url(None)
|
||||
assert website_widget.url() == QUrl("https://scilog.psi.ch")
|
||||
|
||||
website_widget.set_url("https://google.com")
|
||||
assert website_widget.get_url() == "https://google.com"
|
||||
Reference in New Issue
Block a user