mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-04-09 10:10:55 +02:00
Compare commits
14 Commits
feat/bash-
...
docs/add_t
| Author | SHA1 | Date | |
|---|---|---|---|
| bdbc2b903d | |||
| 2a36d9364f | |||
| 27426ce7a5 | |||
|
|
69adadd6d7 | ||
| 6f96498de6 | |||
| 836b6e64f6 | |||
|
|
fab7dd7eec | ||
| 9263f8ef5c | |||
|
|
658728efef | ||
| 6b8432f5b2 | |||
| bc709c4184 | |||
| b49462abeb | |||
| d9d4e3c9bf | |||
| fe04dd80e5 |
@@ -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:
|
||||
@@ -32,21 +39,21 @@ stages:
|
||||
- Deploy
|
||||
|
||||
.install-qt-webengine-deps: &install-qt-webengine-deps
|
||||
- apt-get -y install libnss3 libxdamage1 libasound2 libatomic1 libxcursor1
|
||||
- export QTWEBENGINE_DISABLE_SANDBOX=1
|
||||
- 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
|
||||
|
||||
- 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
|
||||
- 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
|
||||
@@ -92,10 +99,10 @@ pylint-check:
|
||||
- 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 origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME);
|
||||
CHANGED_FILES=$(git diff --name-only $TARGET_BRANCH_COMMIT_SHA HEAD | 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
|
||||
|
||||
@@ -120,7 +127,7 @@ tests:
|
||||
stage: test
|
||||
needs: []
|
||||
variables:
|
||||
QT_QPA_PLATFORM: "offscreen"
|
||||
QT_QPA_PLATFORM: "offscreen"
|
||||
script:
|
||||
- *clone-repos
|
||||
- *install-os-packages
|
||||
@@ -141,21 +148,21 @@ tests:
|
||||
test-matrix:
|
||||
parallel:
|
||||
matrix:
|
||||
- PYTHON_VERSION:
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
QT_PCKG:
|
||||
- "pyside6"
|
||||
- "pyqt5"
|
||||
- "pyqt6"
|
||||
- PYTHON_VERSION:
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
QT_PCKG:
|
||||
- "pyside6"
|
||||
- "pyqt5"
|
||||
- "pyqt6"
|
||||
|
||||
stage: AdditionalTests
|
||||
needs: []
|
||||
variables:
|
||||
QT_QPA_PLATFORM: "offscreen"
|
||||
PYTHON_VERSION: ""
|
||||
QT_PCKG: ""
|
||||
QT_QPA_PLATFORM: "offscreen"
|
||||
PYTHON_VERSION: ""
|
||||
QT_PCKG: ""
|
||||
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:$PYTHON_VERSION
|
||||
script:
|
||||
- *clone-repos
|
||||
@@ -226,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
|
||||
@@ -242,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"'
|
||||
|
||||
89
CHANGELOG.md
89
CHANGELOG.md
@@ -2,6 +2,56 @@
|
||||
|
||||
|
||||
|
||||
## 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
|
||||
@@ -124,42 +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))
|
||||
|
||||
@@ -17,6 +17,7 @@ class Widgets(str, enum.Enum):
|
||||
BECDockArea = "BECDockArea"
|
||||
BECFigure = "BECFigure"
|
||||
SpiralProgressBar = "SpiralProgressBar"
|
||||
TextBox = "TextBox"
|
||||
WebsiteWidget = "WebsiteWidget"
|
||||
|
||||
|
||||
@@ -1897,6 +1898,48 @@ class SpiralProgressBar(RPCBase):
|
||||
"""
|
||||
|
||||
|
||||
class StopButton(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
def config_dict(self) -> "dict":
|
||||
"""
|
||||
Get the configuration of the widget.
|
||||
|
||||
Returns:
|
||||
dict: The configuration of the widget.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def get_all_rpc(self) -> "dict":
|
||||
"""
|
||||
Get all registered RPC objects.
|
||||
"""
|
||||
|
||||
|
||||
class TextBox(RPCBase):
|
||||
@rpc_call
|
||||
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.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_text(self, text: str) -> None:
|
||||
"""
|
||||
Set the text of the Widget
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def set_font_size(self, size: int) -> None:
|
||||
"""
|
||||
Set the font size of the text in the Widget.
|
||||
"""
|
||||
|
||||
|
||||
class WebsiteWidget(RPCBase):
|
||||
@rpc_call
|
||||
def set_url(self, url: str) -> None:
|
||||
|
||||
@@ -86,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",
|
||||
@@ -100,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
|
||||
@@ -176,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:
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from bec_widgets.utils import BECConnector
|
||||
from bec_widgets.widgets.figure import BECFigure
|
||||
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
|
||||
|
||||
|
||||
@@ -11,6 +12,7 @@ class RPCWidgetHandler:
|
||||
"BECFigure": BECFigure,
|
||||
"SpiralProgressBar": SpiralProgressBar,
|
||||
"Website": WebsiteWidget,
|
||||
"TextBox": TextBox,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -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()
|
||||
|
||||
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())
|
||||
@@ -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: user.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: user.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/
|
||||
```
|
||||
353
docs/developer/widgets/how_to_develop_a_widget.md
Normal file
353
docs/developer/widgets/how_to_develop_a_widget.md
Normal file
@@ -0,0 +1,353 @@
|
||||
(developer.widgets.how_to_develop_a_widget)=
|
||||
# How to Develop a Widget
|
||||
This section provides a step-by-step guide on how to develop a new widget for BEC Widgets. We will develop a simple widget that allows you to press a button and specify a user-defined action. The general widget will be based on a [QPushButton](https://doc.qt.io/qt-6/qpushbutton.html) which we will extend to be capable of communicating with BEC through the interface provided by BEC Widgets.
|
||||
|
||||
## Button to start a scan
|
||||
Developing a new widget in BEC Widgets is straightforward. Let's create a widget that allows a user to press a button and execute a `line_scan` in BEC. The proper location to create a new widget is either in the `bec_widgets/widgets` directory, or the beamline plugin widget direction, i.e. `csaxs_bec/bec_widgets`, depending on where your development takes place.
|
||||
|
||||
### Step 1: Create a new widget class
|
||||
|
||||
We first create a simple class that inherits from the `QPushButton` class.
|
||||
The following code snippet demonstrates how to create a new widget:
|
||||
|
||||
``` python
|
||||
from qtpy.QtWidgets import QPushButton
|
||||
|
||||
class StartScanButton(QPushButton):
|
||||
def __init__(self, parent=None):
|
||||
QPushButton.__init__(self, parent=parent)
|
||||
# Connect the button to the on_click method
|
||||
self.clicked.connect(self.on_click)
|
||||
|
||||
def on_click(self):
|
||||
pass
|
||||
```
|
||||
So far we have created the button, but we have not yet put any logic to the `on_click` event of the button.
|
||||
Adding the functionality to be able to execute a scans will be tackled in the next step.
|
||||
|
||||
````{note}
|
||||
To make the button work as a standalone application, you can simply add the following lines at the end.
|
||||
``` python
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
import sys
|
||||
|
||||
from qtpy.QtWidgets import QApplication
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
widget = StartScanButton()
|
||||
widget.show()
|
||||
sys.exit(app.exec_())
|
||||
```
|
||||
````
|
||||
|
||||
|
||||
### Step 2: Connect with BEC, implement *on_click* functionality
|
||||
To be able to start a scan, we need to communicate with BEC. This can be facilitated easily by inheriting additionally from [`BECConnector`](../../api_reference/_autosummary/bec_widgets.utils.bec_connector.BECConnector).
|
||||
With the *BECConnector*, we will also have to pass the *client* ([BECClient](https://bec.readthedocs.io/en/latest/api_reference/_autosummary/bec_lib.client.BECClient.html)) and the *gui_id* (str) to init function of both, our *StartScanButton* widget and the `super().__init__(client=client, gui_id=gui_id)` call.
|
||||
In the init of *BECConnector*, the client will be initialised and stored in `self.client`, which gives us access to the available scan objects via `self.client.scans`.
|
||||
|
||||
``` python
|
||||
from qtpy.QtWidgets import QPushButton
|
||||
from bec_widgets.utils import BECConnector
|
||||
|
||||
class StartScanButton(BECConnector, QPushButton):
|
||||
def __init__(self, parent=None, client:=None, gui_id=None):
|
||||
super().__init__(client=client, gui_id=gui_id)
|
||||
QPushButton.__init__(self, parent=parent)
|
||||
|
||||
# Set a default scan command, args and kwargs
|
||||
self.scan_name = "line_scan"
|
||||
self.scan_args = (dev.samx, -5, 5)
|
||||
self.scan_kwargs = {"steps": 50, "exp_time": 0.1, "relative": True}
|
||||
# Set the text of the button to display the current scan name
|
||||
self.set_button_text()
|
||||
# Connect the button to the on_click method
|
||||
self.clicked.connect(self.on_click)
|
||||
|
||||
def set_button_text(self):
|
||||
"""Set the text of the button"""
|
||||
self.setText(f"Start {self.scan_name}")
|
||||
|
||||
def run_command(self):
|
||||
"""Run the scan command."""
|
||||
# Get the scan command from the scans library
|
||||
scan_command = getattr(self.client.scans, self.scan_name)
|
||||
# Run the scan command
|
||||
scan_report = scan_command(*self.scan_args, **self.scan_kwargs)
|
||||
# Wait for the scan to finish
|
||||
scan_report.wait()
|
||||
|
||||
def on_click(self):
|
||||
"""Start a line scan"""
|
||||
self.run_command()
|
||||
```
|
||||
|
||||
```{note}
|
||||
For the args and kwargs of the scan command, we are using the same syntax as in the client: `dev.samx` is not a string but the same object as in the client.
|
||||
```
|
||||
In the *run_command* method, we retrieve the scan object from the client by its name, and execute the method with all *args* and *kwargs* that we have set.
|
||||
The current implementation of *run_command* is a blocking call due to `scan_report.wait()`, which is not ideal for a GUI application since it freezes the GUI. We will adress this in the next step.
|
||||
|
||||
### Step 3: Improving the widget interactivity
|
||||
To not freeze the GUI, we need to run the scan command in a separate thread. We can either use [QThreads](https://doc.qt.io/qtforpython-6/PySide6/QtCore/QThread.html) or the Python [threading module](https://docs.python.org/3/library/threading.html#thread-objects). In this example, we will use the Python threading module. In addition, we add a method `update_style` to change the style of the button to indicate to the user that the scan is running. We also extend the cleanup procedure of `BECConnector` to ensure that the thread is stopped when the widget is closed. This is good practice to avoid having threads running in the background when the widget is closed.
|
||||
|
||||
``` python
|
||||
|
||||
def update_style(self, mode: Literal["ready", "running"]):
|
||||
"""Update the style of the button based on the mode.
|
||||
|
||||
Args:
|
||||
mode (Literal["ready", "running"): The mode of the button.
|
||||
"""
|
||||
if mode == "ready":
|
||||
self.setStyleSheet(
|
||||
"background-color: #4CAF50; color: white; font-size: 16px; padding: 10px 24px;"
|
||||
)
|
||||
elif mode == "running":
|
||||
self.setStyleSheet(
|
||||
"background-color: #808080; color: white; font-size: 16px; padding: 10px 24px;"
|
||||
)
|
||||
|
||||
def run_command(self):
|
||||
"""Run the scan command."""
|
||||
# Switch the style of the button
|
||||
self.update_style("running")
|
||||
# Disable the buttom while the scan is running
|
||||
self.setEnabled(False)
|
||||
# Get the scan command from the scans library
|
||||
scan_command = getattr(self.scans, self.scan_name)
|
||||
# Run the scan command
|
||||
scan_report = scan_command(*self.scan_args, **self.scan_kwargs)
|
||||
# Wait for the scan to finish
|
||||
scan_report.wait()
|
||||
# Reactivate the button
|
||||
self.setEnabled(True)
|
||||
# Switch the style of the button back to ready
|
||||
self.update_style("ready")
|
||||
|
||||
def on_click(self):
|
||||
"""Start a line scan"""
|
||||
thread = threading.Thread(target=self.run_command)
|
||||
thread.start()
|
||||
|
||||
def cleanup(self):
|
||||
"""Cleanup the widget"""
|
||||
# stop thread
|
||||
# stop the thread or if this is implemented via QThread, ensure stopping of QThread.
|
||||
# Ideally, the BECConnector should take care of this automatically.
|
||||
# Important to call super().cleanup() to ensure that the cleanup of the BECConnector is also called
|
||||
super().cleanup()
|
||||
```
|
||||
We now added started the scan in a separate thread, which allows the GUI to remain responsive. We also added a method to change the style of the button to indicate to the user that the scan is running. The cleanup method ensures that the thread is stopped when the widget is closed. In a last step, we know like to make the scan command configurable.
|
||||
|
||||
### Step 4: Make the scan command configurable
|
||||
In order to make the scan comman configurable, we implement a method `set_scan_command` which allows the user to set the scan command, arguments and keyword arguments.
|
||||
This method should also become available through the RPC interface of BEC Widgets, so we add the class attribute `USER_ACCESS` which is a list of strings with functions that should become available for the CLI.
|
||||
|
||||
``` python
|
||||
def set_scan_command(
|
||||
self, scan_name: str, args: tuple, kwargs: dict
|
||||
):
|
||||
"""Set the scan command to run.
|
||||
|
||||
Args:
|
||||
scan_name (str): The name of the scan command.
|
||||
args (tuple): The arguments for the scan command.
|
||||
kwargs (dict): The keyword arguments for the scan command.
|
||||
"""
|
||||
# check if scan_command starts with scans.
|
||||
if not getattr(self.client.scans, scan_name):
|
||||
raise ValueError(
|
||||
f"The scan type must be implemented in the scan library of BEC, received {scan_name}"
|
||||
)
|
||||
self.scan_name = scan_name
|
||||
self.scan_args = args
|
||||
self.scan_kwargs = kwargs
|
||||
self.set_button_text()
|
||||
```
|
||||
|
||||
### Step 5: Generate client interface for RPC
|
||||
We have now prepared the widget which is fully functional as a standalone widget. But we also want to make it available to the BEC command-line-interface (CLI), for which we prepared the **USER_ACCESS** class attribute.
|
||||
The communication between the BEC IPythonClient and the widget is done vie the RPC interface of BEC Widgets.
|
||||
For this, we need to run the `bec_widgets.cli.generate_cli` script to generate the CLI interface.
|
||||
|
||||
``` bash
|
||||
python bec_widgets.cli.generate_cli --core
|
||||
# alternatively use the entry point from BEC Widgets
|
||||
bw-generate-cli
|
||||
```
|
||||
|
||||
This will generate a new client with all relevant methods in [`bec_widgets.cli.client.py`](../../api_reference/_autosummary/bec_widgets.bec_widgets.cli.client.rst).
|
||||
The last step is to make the RPCWidgetHandler class aware of the widget, which means to add the name of the widget to the widgets list in the [`RPCWidgetHandler`](../../api_reference/_autosummary/bec_widgets.bec_widgets.cli.rpc_widget_handler.RPCWidgetHandler.rst) class.
|
||||
|
||||
````{dropdown} View code: RPCWidgetHandler class
|
||||
:icon: code-square
|
||||
:animate: fade-in-slide-down
|
||||
|
||||
```{literalinclude} ../../../bec_widgets/cli/rpc_widget_handler.py
|
||||
:language: python
|
||||
:pyobject: RPCWidgetHandler
|
||||
```
|
||||
````
|
||||
|
||||
With this, we have a fully functional widget that allows the user to start a scan with a button. The scan command, arguments and keyword arguments can be set by the user.
|
||||
The full code is shown once again below:
|
||||
|
||||
````{dropdown} View code: Full code of the StartScanButton widget
|
||||
:icon: code-square
|
||||
:animate: fade-in-slide-down
|
||||
|
||||
```
|
||||
import threading
|
||||
from typing import Literal
|
||||
|
||||
from qtpy.QtWidgets import QPushButton
|
||||
|
||||
from bec_widgets.utils import BECConnector
|
||||
|
||||
|
||||
class StartScanButton(BECConnector, QPushButton):
|
||||
"""A button to start a line scan.
|
||||
|
||||
Args:
|
||||
parent: The parent widget.
|
||||
client (BECClient): The BEC client.
|
||||
gui_id (str): The unique ID of the widget.
|
||||
"""
|
||||
|
||||
USER_ACCESS = ["set_scan_command"]
|
||||
|
||||
def __init__(self, parent=None, client=None, gui_id=None):
|
||||
super().__init__(client=client, gui_id=gui_id)
|
||||
QPushButton.__init__(self, parent=parent)
|
||||
|
||||
# Set the scan command to None
|
||||
self.scan_command = None
|
||||
# Set default scan command
|
||||
self.scan_name = "line_scan"
|
||||
self.scan_args = (dev.samx, -5, 5)
|
||||
self.scan_kwargs = {"steps": 50, "exp_time": 0.1, "relative": True}
|
||||
# Set the text of the button
|
||||
self.set_button_text()
|
||||
# Set the style of the button
|
||||
self.update_style("ready")
|
||||
# Connect the button to the on_click method
|
||||
self.clicked.connect(self.on_click)
|
||||
|
||||
def update_style(self, mode: Literal["ready", "running"]):
|
||||
"""Update the style of the button based on the mode.
|
||||
|
||||
Args:
|
||||
mode (Literal["ready", "running"): The mode of the button.
|
||||
"""
|
||||
if mode == "ready":
|
||||
self.setStyleSheet(
|
||||
"background-color: #4CAF50; color: white; font-size: 16px; padding: 10px 24px;"
|
||||
)
|
||||
elif mode == "running":
|
||||
self.setStyleSheet(
|
||||
"background-color: #808080; color: white; font-size: 16px; padding: 10px 24px;"
|
||||
)
|
||||
|
||||
def set_button_text(self):
|
||||
"""Set the text of the button."""
|
||||
self.setText(f"Start {self.scan_name}")
|
||||
|
||||
def set_scan_command(self, scan_name: str, args: tuple, kwargs: dict):
|
||||
"""Set the scan command to run.
|
||||
|
||||
Args:
|
||||
scan_name (str): The name of the scan command.
|
||||
args (tuple): The arguments for the scan command.
|
||||
kwargs (dict): The keyword arguments for the scan command.
|
||||
"""
|
||||
# check if scan_command starts with scans.
|
||||
if not getattr(self.client.scans, scan_name):
|
||||
raise ValueError(
|
||||
f"The scan type must be implemented in the scan library of BEC, received {scan_name}"
|
||||
)
|
||||
self.scan_name = scan_name
|
||||
self.scan_args = args
|
||||
self.scan_kwargs = kwargs
|
||||
self.set_button_text()
|
||||
|
||||
def run_command(self):
|
||||
"""Run the scan command."""
|
||||
# Switch the style of the button
|
||||
self.update_style("running")
|
||||
# Disable the buttom while the scan is running
|
||||
self.setEnabled(False)
|
||||
# Get the scan command from the scans library
|
||||
scan_command = getattr(self.scans, self.scan_name)
|
||||
# Run the scan command
|
||||
scan_report = scan_command(*self.scan_args, **self.scan_kwargs)
|
||||
# Wait for the scan to finish
|
||||
scan_report.wait()
|
||||
# Reactivate the button
|
||||
self.setEnabled(True)
|
||||
# Switch the style of the button back to ready
|
||||
self.update_style("ready")
|
||||
|
||||
def on_click(self):
|
||||
"""Start a line scan"""
|
||||
thread = threading.Thread(target=self.run_command)
|
||||
thread.start()
|
||||
|
||||
def cleanup(self):
|
||||
"""Cleanup the widget"""
|
||||
# stop thread
|
||||
# stop the thread or if this is implemented via QThread, ensure stopping of QThread.
|
||||
# Ideally, the BECConnector should take care of this automatically.
|
||||
# Important to call super().cleanup() to ensure that the cleanup of the BECConnector is also called
|
||||
super().cleanup()
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
import sys
|
||||
|
||||
from qtpy.QtWidgets import QApplication
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
widget = StartScanButton()
|
||||
widget.show()
|
||||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
```
|
||||
````
|
||||
|
||||
### Step 6: Write a test for the widget
|
||||
We highly recommend writing tests for the widget to ensure that they work as expected. This allows to run the tests automatically in a CI/CD pipeline and to ensure that the widget works as expected not only now but als in the future.
|
||||
The following code snippet shows an example to test the set_scan_command from the `StartScanButton` widget.
|
||||
``` python
|
||||
import pytest
|
||||
|
||||
from bec_widgets.widgets.start_scan_button import StartScanButton
|
||||
|
||||
from .client_mocks import mocked_client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_scan_button(qtbot, mocked_client):
|
||||
widget = StartScanButton(client=mocked_client)
|
||||
qtbot.addWidget(widget)
|
||||
qtbot.waitExposed(widget)
|
||||
yield widget
|
||||
widget.close()
|
||||
|
||||
|
||||
def test_set_scan_command(test_scan_button):
|
||||
"""Test the set_scan_command function."""
|
||||
test_scan_button.set_scan_command(
|
||||
scan_name="grid_scan",
|
||||
args=(dev.samx, -5, 5, 10, dev.samy, -5, 5, 20),
|
||||
kwargs={"exp_time": 0.1, "relative": True},
|
||||
)
|
||||
# Check first if all parameter have been properly set
|
||||
assert test_scan_button.scan_name == "grid_scan"
|
||||
assert test_scan_button.scan_args == (dev.samx, -5, 5, 10, dev.samy, -5, 5, 20)
|
||||
assert test_scan_button.scan_kwargs == {"exp_time": 0.1, "relative": True}
|
||||
# Next, we check if the displayed text of the button has been updated
|
||||
# We use the .text() method from the QPushButton class to retrieve the text displayed
|
||||
assert test_scan_button.text() == "Start grid_scan"
|
||||
```
|
||||
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/
|
||||
```
|
||||
@@ -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
|
||||
|
||||
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>")
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ bec_figure/
|
||||
spiral_progress_bar/
|
||||
website/
|
||||
buttons/
|
||||
text_box/
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "bec_widgets"
|
||||
version = "0.62.0"
|
||||
version = "0.63.2"
|
||||
description = "BEC Widgets"
|
||||
requires-python = ">=3.10"
|
||||
classifiers = [
|
||||
@@ -48,6 +48,7 @@ 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 = ["*"]
|
||||
|
||||
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"
|
||||
)
|
||||
Reference in New Issue
Block a user