0
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-07-14 03:31:50 +02:00

fix: refactor textbox widget, remove inheritance, adhere to bec style; closes #324

This commit is contained in:
2024-09-06 18:33:36 +02:00
parent 774044d2a7
commit b0d786b991
7 changed files with 104 additions and 146 deletions

View File

@ -2914,31 +2914,21 @@ class StopButton(RPCBase):
class TextBox(RPCBase): class TextBox(RPCBase):
@rpc_call @rpc_call
def set_color(self, background_color: str, font_color: str) -> None: def set_plain_text(self, text: str) -> None:
""" """
Set the background color of the widget. Set the plain text 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.
Args: Args:
text (str): The text to set. text (str): The text to set.
""" """
@rpc_call @rpc_call
def set_font_size(self, size: int) -> None: def set_html_text(self, text: str) -> None:
""" """
Set the font size of the text in the widget. Set the HTML text of the widget.
Args: Args:
size (int): The font size to set. text (str): The text to set.
""" """

View File

@ -1,39 +1,47 @@
import re """Module for a text box widget that displays text in plain and HTML format and adheres to the BECWidget interface & style."""
from pydantic import Field, field_validator import re
from qtpy.QtWidgets import QTextEdit from html.parser import HTMLParser
from bec_lib.logger import bec_logger
from pydantic import Field
from qtpy.QtCore import Property, Slot
from qtpy.QtWidgets import QTextEdit, QVBoxLayout, QWidget
from bec_widgets.utils.bec_connector import ConnectionConfig from bec_widgets.utils.bec_connector import ConnectionConfig
from bec_widgets.utils.bec_widget import BECWidget from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.colors import Colors
logger = bec_logger.logger
DEFAULT_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>"
class TextBoxConfig(ConnectionConfig): class TextBoxConfig(ConnectionConfig):
"""Configuration for the TextBox widget.
theme: str = Field("dark", description="The theme of the figure widget.") Args:
font_color: str = Field("#FFF", description="The font color of the text") text (str, optional): The text to display in the widget. Defaults to None.
background_color: str = Field("#000", description="The background color of the widget.") is_html (bool, optional): Whether the text is in HTML format or not. Defaults to False.
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 text: str | None = Field(None, description="The text to display in the widget.")
@field_validator("theme") is_html: bool = Field(False, description="Whether the text is in HTML format or not.")
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(BECWidget, QTextEdit): class TextBox(BECWidget, QWidget):
"""A widget that displays text in plain and HTML format
USER_ACCESS = ["set_color", "set_text", "set_font_size"] Args:
parent (QWidget, optional): The parent widget. Defaults to None.
client ([type], optional): The client to use. Defaults to None.
config ([type], optional): The config to use. Defaults to None.
gui_id ([type], optional): The gui_id to use. Defaults to None.
"""
USER_ACCESS = ["set_plain_text", "set_html_text"]
ICON_NAME = "chat" ICON_NAME = "chat"
def __init__(self, parent=None, text: str = "", client=None, config=None, gui_id=None): def __init__(self, parent=None, client=None, config=None, gui_id=None):
if config is None: if config is None:
config = TextBoxConfig(widget_class=self.__class__.__name__) config = TextBoxConfig(widget_class=self.__class__.__name__)
else: else:
@ -41,80 +49,79 @@ class TextBox(BECWidget, QTextEdit):
config = TextBoxConfig(**config) config = TextBoxConfig(**config)
self.config = config self.config = config
super().__init__(client=client, config=config, gui_id=gui_id) super().__init__(client=client, config=config, gui_id=gui_id)
QTextEdit.__init__(self, parent=parent) QWidget.__init__(self, parent)
self.layout = QVBoxLayout(self)
self.text_box_text_edit = QTextEdit(parent=self)
self.layout.addWidget(self.text_box_text_edit)
self.setLayout(self.layout)
self.layout.setContentsMargins(0, 0, 0, 0)
self.config = config self.config = config
self.setReadOnly(True) self.text_box_text_edit.setReadOnly(True)
self.setGeometry(self.rect()) if self.config.text is not None:
self.set_color(self.config.background_color, self.config.font_color) if self.config.is_html:
if not text: self.set_html_text(self.config.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>" else:
self.set_text(text) self.set_plain_text(self.config.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: else:
theme = "dark" self.set_html_text(DEFAULT_TEXT)
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: @Slot(str)
"""Set the background color of the widget. def set_plain_text(self, text: str) -> None:
"""Set the plain text of the widget.
Args: Args:
background_color (str): The color to set the background in HEX. text (str): The text to set.
font_color (str): The color to set the font in HEX.
""" """
self.config.background_color = background_color self.text_box_text_edit.setPlainText(text)
self.config.font_color = font_color self.config.text = text
self._update_stylesheet() self.config.is_html = False
def set_font_size(self, size: int) -> None: @Slot(str)
"""Set the font size of the text in the widget. def set_html_text(self, text: str) -> None:
"""Set the HTML text of the widget.
Args: Args:
size (int): The font size to set. text (str): The text to set.
""" """
self.config.font_size = size self.text_box_text_edit.setHtml(text)
self._update_stylesheet() self.config.text = text
self.config.is_html = True
def _update_stylesheet(self): @Property(str)
"""Update the stylesheet of the widget.""" def plain_text(self) -> str:
self.setStyleSheet( """Get the text of the widget.
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: Returns:
str: The text of the widget.
"""
return self.text_box_text_edit.toPlainText()
@plain_text.setter
def plain_text(self, text: str) -> None:
"""Set the text of the widget. """Set the text of the widget.
Args: Args:
text (str): The text to set. text (str): The text to set.
""" """
if self.is_html(text): self.set_plain_text(text)
self.setHtml(text)
else:
self.setPlainText(text)
self.config.text = text
def is_html(self, text: str) -> bool: @Property(str)
"""Check if the text contains HTML tags. def html_text(self) -> str:
"""Get the HTML text of the widget.
Args:
text (str): The text to check.
Returns: Returns:
bool: True if the text contains HTML tags, False otherwise. str: The HTML text of the widget.
""" """
return bool(re.search(r"<[a-zA-Z/][^>]*>", text)) return self.text_box_text_edit.toHtml()
@html_text.setter
def html_text(self, text: str) -> None:
"""Set the HTML text of the widget.
Args:
text (str): The HTML text to set.
"""
self.set_html_text(text)
if __name__ == "__main__": if __name__ == "__main__":
@ -123,7 +130,6 @@ if __name__ == "__main__":
from qtpy.QtWidgets import QApplication from qtpy.QtWidgets import QApplication
app = QApplication(sys.argv) app = QApplication(sys.argv)
widget = TextBox() widget = TextBox()
widget.show() widget.show()
sys.exit(app.exec()) sys.exit(app.exec())

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View File

@ -36,7 +36,7 @@ waveform.dap_summary_update.connect(lmfit_dialog.update_summary_tree)
```` ````
````{tab} API ````{tab} API
```{eval-rst} ```{eval-rst}
.. include:: /api_reference/_autosummary/bec_widgets.cli.client.LMFitDialog.rst .. include:: /api_reference/_autosummary/bec_widgets.widgets.lmfit_dialog.lmfit_dialog.LMFitDialog.rst
``` ```
```` ````

View File

@ -30,6 +30,7 @@ Within the BECDesigner's [property editor](https://doc.qt.io/qt-6/designer-widge
- **Hide Scan Control**: Allows you to hide the scan control buttons from the widget interface. This is useful when you want to place the control buttons in a different location. - **Hide Scan Control**: Allows you to hide the scan control buttons from the widget interface. This is useful when you want to place the control buttons in a different location.
- **Hide Scan Selection**: Allows you to hide the scan selection combobox from the widget interface. This is useful when you want to restrict the user to a specific scan type or implement a custom scan selection mechanism. - **Hide Scan Selection**: Allows you to hide the scan selection combobox from the widget interface. This is useful when you want to restrict the user to a specific scan type or implement a custom scan selection mechanism.
- **Hide Scan Remember Toggle**: Allows you to hide the toggle button that reloads scan parameters from the last executed scan. This is useful if you want to disable or restrict this functionality in specific scenarios. - **Hide Scan Remember Toggle**: Allows you to hide the toggle button that reloads scan parameters from the last executed scan. This is useful if you want to disable or restrict this functionality in specific scenarios.
- **Hide Bundle Buttons**: Allows you to hide the buttons that add or remove argument bundles from the widget interface. This is useful when you want to restrict the user from adding additional motor bundles to the scan by accident.
**BEC Designer properties:** **BEC Designer properties:**
```{figure} ./hide_scan_control.png ```{figure} ./hide_scan_control.png

View File

@ -8,8 +8,11 @@ The [`Text Box Widget`](/api_reference/_autosummary/bec_widgets.cli.client.TextB
## Key Features: ## Key Features:
- **Text Display**: Display either plain text or HTML content, with automatic detection of the format. - **Text Display**: Display either plain text or HTML content, with automatic detection of the format.
- **Customizable Appearance**: Set the background and font colors to match the design of your application. - **Automatic styling**: The widget automatically adheres to BEC's style guides. No need to worry about background colors, font sizes, or other appearance settings.
- **Font Size Adjustment**: Customize the font size of the displayed text for better readability.
## BEC Designer Properties
```{figure} ../../assets/widget_screenshots/text_box_properties.png
```
```` ````
@ -38,24 +41,6 @@ The `TextBox` widget can automatically detect and render HTML content. This exam
text_box.set_text("<h1>Welcome to BEC Widgets</h1><p>This is an example of displaying <strong>HTML</strong> text.</p>") text_box.set_text("<h1>Welcome to BEC Widgets</h1><p>This is an example of displaying <strong>HTML</strong> text.</p>")
``` ```
## Example 3 - Customizing Appearance
The `TextBox` widget allows you to customize the background and font colors to fit your application's design. Below is an example of how to set these properties.
```python
# Set the background color to white and the font color to black
text_box.set_color(background_color="#FFF", font_color="#000")
```
## Example 4 - Adjusting Font Size
To improve readability or fit more text within the widget, you can adjust the font size.
```python
# Set the font size to 14 pixels
text_box.set_font_size(14)
```
```` ````
````{tab} API ````{tab} API

View File

@ -1,9 +1,6 @@
import re
from unittest import mock
import pytest import pytest
from bec_widgets.widgets.text_box.text_box import TextBox from bec_widgets.widgets.text_box.text_box import DEFAULT_TEXT, TextBox
from .client_mocks import mocked_client from .client_mocks import mocked_client
@ -18,37 +15,16 @@ def text_box_widget(qtbot, mocked_client):
def test_textbox_widget(text_box_widget): def test_textbox_widget(text_box_widget):
"""Test the TextBox widget.""" """Test the TextBox widget."""
# Test default
assert text_box_widget.config.text == DEFAULT_TEXT
# Test set text
text = "Hello World!" text = "Hello World!"
text_box_widget.set_text(text) text_box_widget.set_plain_text(text)
assert text_box_widget.toPlainText() == text assert text_box_widget.plain_text == text
# Test set HTML 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>" 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_html_text(text)
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 ( assert (
text_box_widget.styleSheet() text_box_widget.plain_text
== f"background-color: #FFF; color: #000; font-size: {text_box_widget.config.font_size}px" == "Welcome to PyQt6\nThis is an example of displaying HTML text."
)
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"
) )