bec-widgets 0.62.0__py3-none-any.whl → 0.63.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- CHANGELOG.md +35 -28
- PKG-INFO +1 -1
- bec_widgets/cli/client.py +43 -0
- bec_widgets/cli/client_utils.py +38 -71
- bec_widgets/cli/rpc_wigdet_handler.py +2 -0
- bec_widgets/examples/jupyter_console/jupyter_console_window.py +5 -6
- bec_widgets/utils/bec_dispatcher.py +1 -5
- bec_widgets/widgets/text_box/__init__.py +0 -0
- bec_widgets/widgets/text_box/text_box.py +127 -0
- {bec_widgets-0.62.0.dist-info → bec_widgets-0.63.1.dist-info}/METADATA +1 -1
- {bec_widgets-0.62.0.dist-info → bec_widgets-0.63.1.dist-info}/RECORD +18 -14
- docs/user/widgets/text_box.md +33 -0
- docs/user/widgets/widgets.md +1 -0
- pyproject.toml +1 -1
- tests/unit_tests/test_text_box_widget.py +55 -0
- {bec_widgets-0.62.0.dist-info → bec_widgets-0.63.1.dist-info}/WHEEL +0 -0
- {bec_widgets-0.62.0.dist-info → bec_widgets-0.63.1.dist-info}/entry_points.txt +0 -0
- {bec_widgets-0.62.0.dist-info → bec_widgets-0.63.1.dist-info}/licenses/LICENSE +0 -0
CHANGELOG.md
CHANGED
@@ -2,6 +2,41 @@
|
|
2
2
|
|
3
3
|
|
4
4
|
|
5
|
+
## v0.63.1 (2024-06-13)
|
6
|
+
|
7
|
+
### Fix
|
8
|
+
|
9
|
+
* fix: just terminate the remote process in close() instead of communicating
|
10
|
+
|
11
|
+
The proper finalization sequence will be executed by the remote process
|
12
|
+
on SIGTERM ([`9263f8e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/9263f8ef5c17ae7a007a1a564baf787b39061756))
|
13
|
+
|
14
|
+
|
15
|
+
## v0.63.0 (2024-06-13)
|
16
|
+
|
17
|
+
### Documentation
|
18
|
+
|
19
|
+
* docs: add documentation ([`bc709c4`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/bc709c4184c985d4e721f9ea7d1b3dad5e9153a7))
|
20
|
+
|
21
|
+
### Feature
|
22
|
+
|
23
|
+
* feat: add textbox widget ([`d9d4e3c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/d9d4e3c9bf73ab2a5629c2867b50fc91e69489ec))
|
24
|
+
|
25
|
+
### Refactor
|
26
|
+
|
27
|
+
* refactor: add pydantic config, add change_theme ([`6b8432f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6b8432f5b20a71175a3537b5f6832b76e3b67d73))
|
28
|
+
|
29
|
+
### Test
|
30
|
+
|
31
|
+
* test: add test for text box ([`b49462a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b49462abeb186e56bac79d2ef0b0add1ef28a1a5))
|
32
|
+
|
33
|
+
### Unknown
|
34
|
+
|
35
|
+
* Revert "feat: implement non-polling, interruptible waiting of gui instruction response with timeout"
|
36
|
+
|
37
|
+
This reverts commit abc6caa2d0b6141dfbe1f3d025f78ae14deddcb3 ([`fe04dd8`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/fe04dd80e59a0e74f7fdea603e0642707ecc7c2a))
|
38
|
+
|
39
|
+
|
5
40
|
## v0.62.0 (2024-06-12)
|
6
41
|
|
7
42
|
### Feature
|
@@ -135,31 +170,3 @@
|
|
135
170
|
|
136
171
|
|
137
172
|
## v0.57.6 (2024-06-06)
|
138
|
-
|
139
|
-
### Fix
|
140
|
-
|
141
|
-
* fix(bar): docstrings extended ([`edb1775`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/edb1775967c3ff0723d0edad2b764f1ffc832b7c))
|
142
|
-
|
143
|
-
|
144
|
-
## v0.57.5 (2024-06-06)
|
145
|
-
|
146
|
-
### Documentation
|
147
|
-
|
148
|
-
* docs(figure): docs adjusted to be compatible with new signature ([`c037b87`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/c037b87675af91b26e8c7c60e76622d4ed4cf5d5))
|
149
|
-
|
150
|
-
### Fix
|
151
|
-
|
152
|
-
* fix(waveform): added .plot method with the same signature as BECFigure.plot ([`8479caf`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/8479caf53a7325788ca264e5bd9aee01f1d4c5a0))
|
153
|
-
|
154
|
-
* 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))
|
155
|
-
|
156
|
-
### Refactor
|
157
|
-
|
158
|
-
* 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))
|
159
|
-
|
160
|
-
|
161
|
-
## v0.57.4 (2024-06-06)
|
162
|
-
|
163
|
-
### Fix
|
164
|
-
|
165
|
-
* fix(docks): set_title do update dock internal _name now ([`15cbc21`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/15cbc21e5bb3cf85f5822d44a2b3665b5aa2f346))
|
PKG-INFO
CHANGED
bec_widgets/cli/client.py
CHANGED
@@ -17,6 +17,7 @@ class Widgets(str, enum.Enum):
|
|
17
17
|
BECDockArea = "BECDockArea"
|
18
18
|
BECFigure = "BECFigure"
|
19
19
|
SpiralProgressBar = "SpiralProgressBar"
|
20
|
+
TextBox = "TextBox"
|
20
21
|
WebsiteWidget = "WebsiteWidget"
|
21
22
|
|
22
23
|
|
@@ -1897,6 +1898,48 @@ class SpiralProgressBar(RPCBase):
|
|
1897
1898
|
"""
|
1898
1899
|
|
1899
1900
|
|
1901
|
+
class StopButton(RPCBase):
|
1902
|
+
@property
|
1903
|
+
@rpc_call
|
1904
|
+
def config_dict(self) -> "dict":
|
1905
|
+
"""
|
1906
|
+
Get the configuration of the widget.
|
1907
|
+
|
1908
|
+
Returns:
|
1909
|
+
dict: The configuration of the widget.
|
1910
|
+
"""
|
1911
|
+
|
1912
|
+
@rpc_call
|
1913
|
+
def get_all_rpc(self) -> "dict":
|
1914
|
+
"""
|
1915
|
+
Get all registered RPC objects.
|
1916
|
+
"""
|
1917
|
+
|
1918
|
+
|
1919
|
+
class TextBox(RPCBase):
|
1920
|
+
@rpc_call
|
1921
|
+
def set_color(self, background_color: str, font_color: str) -> None:
|
1922
|
+
"""
|
1923
|
+
Set the background color of the Widget.
|
1924
|
+
|
1925
|
+
Args:
|
1926
|
+
background_color (str): The color to set the background in HEX.
|
1927
|
+
font_color (str): The color to set the font in HEX.
|
1928
|
+
"""
|
1929
|
+
|
1930
|
+
@rpc_call
|
1931
|
+
def set_text(self, text: str) -> None:
|
1932
|
+
"""
|
1933
|
+
Set the text of the Widget
|
1934
|
+
"""
|
1935
|
+
|
1936
|
+
@rpc_call
|
1937
|
+
def set_font_size(self, size: int) -> None:
|
1938
|
+
"""
|
1939
|
+
Set the font size of the text in the Widget.
|
1940
|
+
"""
|
1941
|
+
|
1942
|
+
|
1900
1943
|
class WebsiteWidget(RPCBase):
|
1901
1944
|
@rpc_call
|
1902
1945
|
def set_url(self, url: str) -> None:
|
bec_widgets/cli/client_utils.py
CHANGED
@@ -14,7 +14,7 @@ from typing import TYPE_CHECKING
|
|
14
14
|
|
15
15
|
from bec_lib.endpoints import MessageEndpoints
|
16
16
|
from bec_lib.utils.import_utils import isinstance_based_on_class_name, lazy_import, lazy_import_from
|
17
|
-
from qtpy.QtCore import
|
17
|
+
from qtpy.QtCore import QCoreApplication
|
18
18
|
|
19
19
|
import bec_widgets.cli.client as client
|
20
20
|
from bec_widgets.cli.auto_updates import AutoUpdates
|
@@ -24,8 +24,6 @@ if TYPE_CHECKING:
|
|
24
24
|
|
25
25
|
from bec_widgets.cli.client import BECDockArea, BECFigure
|
26
26
|
|
27
|
-
from bec_lib.serialization import MsgpackSerialization
|
28
|
-
|
29
27
|
messages = lazy_import("bec_lib.messages")
|
30
28
|
# from bec_lib.connector import MessageObject
|
31
29
|
MessageObject = lazy_import_from("bec_lib.connector", ("MessageObject",))
|
@@ -176,16 +174,11 @@ class BECGuiClientMixin:
|
|
176
174
|
"""
|
177
175
|
Close the figure.
|
178
176
|
"""
|
179
|
-
if self._process is None:
|
180
|
-
return
|
181
|
-
if self.gui_is_alive():
|
182
|
-
self._run_rpc("close", (), wait_for_rpc_response=True)
|
183
|
-
else:
|
184
|
-
self._run_rpc("close", (), wait_for_rpc_response=False)
|
185
|
-
self._process.terminate()
|
186
|
-
self._process_output_processing_thread.join()
|
187
|
-
self._process = None
|
188
177
|
self._client.shutdown()
|
178
|
+
if self._process:
|
179
|
+
self._process.terminate()
|
180
|
+
self._process_output_processing_thread.join()
|
181
|
+
self._process = None
|
189
182
|
|
190
183
|
def print_log(self) -> None:
|
191
184
|
"""
|
@@ -207,48 +200,6 @@ class RPCResponseTimeoutError(Exception):
|
|
207
200
|
)
|
208
201
|
|
209
202
|
|
210
|
-
class QtRedisMessageWaiter:
|
211
|
-
def __init__(self, redis_connector, message_to_wait):
|
212
|
-
self.ev_loop = QEventLoop()
|
213
|
-
self.response = None
|
214
|
-
self.connector = redis_connector
|
215
|
-
self.message_to_wait = message_to_wait
|
216
|
-
self.pubsub = redis_connector._redis_conn.pubsub()
|
217
|
-
self.pubsub.subscribe(self.message_to_wait.endpoint)
|
218
|
-
fd = self.pubsub.connection._sock.fileno()
|
219
|
-
self.notifier = QSocketNotifier(fd, QSocketNotifier.Read)
|
220
|
-
self.notifier.activated.connect(self._pubsub_readable)
|
221
|
-
|
222
|
-
def _msg_received(self, msg_obj):
|
223
|
-
self.response = msg_obj.value
|
224
|
-
self.ev_loop.quit()
|
225
|
-
|
226
|
-
def wait(self, timeout=1):
|
227
|
-
timer = QTimer()
|
228
|
-
timer.singleShot(timeout * 1000, self.ev_loop.quit)
|
229
|
-
self.ev_loop.exec_()
|
230
|
-
timer.stop()
|
231
|
-
self.notifier.setEnabled(False)
|
232
|
-
self.pubsub.close()
|
233
|
-
return self.response
|
234
|
-
|
235
|
-
def _pubsub_readable(self, fd):
|
236
|
-
while True:
|
237
|
-
msg = self.pubsub.get_message()
|
238
|
-
if msg:
|
239
|
-
if msg["type"] == "subscribe":
|
240
|
-
# get_message buffers, so we may already have the answer
|
241
|
-
# let's check...
|
242
|
-
continue
|
243
|
-
else:
|
244
|
-
break
|
245
|
-
else:
|
246
|
-
return
|
247
|
-
channel = msg["channel"].decode()
|
248
|
-
msg = MessageObject(topic=channel, value=MsgpackSerialization.loads(msg["data"]))
|
249
|
-
self.connector._execute_callback(self._msg_received, msg, {})
|
250
|
-
|
251
|
-
|
252
203
|
class RPCBase:
|
253
204
|
def __init__(self, gui_id: str = None, config: dict = None, parent=None) -> None:
|
254
205
|
self._client = BECDispatcher().client
|
@@ -275,7 +226,7 @@ class RPCBase:
|
|
275
226
|
parent = parent._parent
|
276
227
|
return parent
|
277
228
|
|
278
|
-
def _run_rpc(self, method, *args, wait_for_rpc_response=True,
|
229
|
+
def _run_rpc(self, method, *args, wait_for_rpc_response=True, **kwargs):
|
279
230
|
"""
|
280
231
|
Run the RPC call.
|
281
232
|
|
@@ -297,24 +248,16 @@ class RPCBase:
|
|
297
248
|
|
298
249
|
# pylint: disable=protected-access
|
299
250
|
receiver = self._root._gui_id
|
300
|
-
if wait_for_rpc_response:
|
301
|
-
redis_msg = QtRedisMessageWaiter(
|
302
|
-
self._client.connector, MessageEndpoints.gui_instruction_response(request_id)
|
303
|
-
)
|
304
|
-
|
305
251
|
self._client.connector.set_and_publish(MessageEndpoints.gui_instructions(receiver), rpc_msg)
|
306
252
|
|
307
|
-
if wait_for_rpc_response:
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
raise ValueError(response.message["error"])
|
316
|
-
msg_result = response.message.get("result")
|
317
|
-
return self._create_widget_from_msg_result(msg_result)
|
253
|
+
if not wait_for_rpc_response:
|
254
|
+
return None
|
255
|
+
response = self._wait_for_response(request_id)
|
256
|
+
# get class name
|
257
|
+
if not response.content["accepted"]:
|
258
|
+
raise ValueError(response.content["message"]["error"])
|
259
|
+
msg_result = response.content["message"].get("result")
|
260
|
+
return self._create_widget_from_msg_result(msg_result)
|
318
261
|
|
319
262
|
def _create_widget_from_msg_result(self, msg_result):
|
320
263
|
if msg_result is None:
|
@@ -337,6 +280,30 @@ class RPCBase:
|
|
337
280
|
return cls(parent=self, **msg_result)
|
338
281
|
return msg_result
|
339
282
|
|
283
|
+
def _wait_for_response(self, request_id: str, timeout: int = 5):
|
284
|
+
"""
|
285
|
+
Wait for the response from the server.
|
286
|
+
|
287
|
+
Args:
|
288
|
+
request_id(str): The request ID.
|
289
|
+
timeout(int): The timeout in seconds.
|
290
|
+
|
291
|
+
Returns:
|
292
|
+
The response from the server.
|
293
|
+
"""
|
294
|
+
start_time = time.time()
|
295
|
+
response = None
|
296
|
+
|
297
|
+
while response is None and self.gui_is_alive() and (time.time() - start_time) < timeout:
|
298
|
+
response = self._client.connector.get(
|
299
|
+
MessageEndpoints.gui_instruction_response(request_id)
|
300
|
+
)
|
301
|
+
QCoreApplication.processEvents() # keep UI responsive (and execute signals/slots)
|
302
|
+
if response is None and (time.time() - start_time) >= timeout:
|
303
|
+
raise RPCResponseTimeoutError(request_id, timeout)
|
304
|
+
|
305
|
+
return response
|
306
|
+
|
340
307
|
def gui_is_alive(self):
|
341
308
|
"""
|
342
309
|
Check if the GUI is alive.
|
@@ -1,6 +1,7 @@
|
|
1
1
|
from bec_widgets.utils import BECConnector
|
2
2
|
from bec_widgets.widgets.figure import BECFigure
|
3
3
|
from bec_widgets.widgets.spiral_progress_bar.spiral_progress_bar import SpiralProgressBar
|
4
|
+
from bec_widgets.widgets.text_box.text_box import TextBox
|
4
5
|
from bec_widgets.widgets.website.website import WebsiteWidget
|
5
6
|
|
6
7
|
|
@@ -11,6 +12,7 @@ class RPCWidgetHandler:
|
|
11
12
|
"BECFigure": BECFigure,
|
12
13
|
"SpiralProgressBar": SpiralProgressBar,
|
13
14
|
"Website": WebsiteWidget,
|
15
|
+
"TextBox": TextBox,
|
14
16
|
}
|
15
17
|
|
16
18
|
@staticmethod
|
@@ -136,18 +136,17 @@ if __name__ == "__main__": # pragma: no cover
|
|
136
136
|
|
137
137
|
module_path = os.path.dirname(bec_widgets.__file__)
|
138
138
|
|
139
|
+
bec_dispatcher = BECDispatcher()
|
140
|
+
client = bec_dispatcher.client
|
141
|
+
client.start()
|
142
|
+
|
139
143
|
app = QApplication(sys.argv)
|
140
144
|
app.setApplicationName("Jupyter Console")
|
141
145
|
app.setApplicationDisplayName("Jupyter Console")
|
142
|
-
qdarktheme.setup_theme("auto")
|
146
|
+
# qdarktheme.setup_theme("auto")
|
143
147
|
icon = QIcon()
|
144
148
|
icon.addFile(os.path.join(module_path, "assets", "terminal_icon.png"), size=QSize(48, 48))
|
145
149
|
app.setWindowIcon(icon)
|
146
|
-
|
147
|
-
bec_dispatcher = BECDispatcher()
|
148
|
-
client = bec_dispatcher.client
|
149
|
-
client.start()
|
150
|
-
|
151
150
|
win = JupyterConsoleWindow()
|
152
151
|
win.show()
|
153
152
|
|
@@ -9,7 +9,7 @@ import redis
|
|
9
9
|
from bec_lib.client import BECClient
|
10
10
|
from bec_lib.redis_connector import MessageObject, RedisConnector
|
11
11
|
from bec_lib.service_config import ServiceConfig
|
12
|
-
from qtpy.QtCore import
|
12
|
+
from qtpy.QtCore import QObject
|
13
13
|
from qtpy.QtCore import Signal as pyqtSignal
|
14
14
|
|
15
15
|
if TYPE_CHECKING:
|
@@ -71,7 +71,6 @@ class BECDispatcher:
|
|
71
71
|
|
72
72
|
_instance = None
|
73
73
|
_initialized = False
|
74
|
-
qapp = None
|
75
74
|
|
76
75
|
def __new__(cls, client=None, config: str = None, *args, **kwargs):
|
77
76
|
if cls._instance is None:
|
@@ -83,9 +82,6 @@ class BECDispatcher:
|
|
83
82
|
if self._initialized:
|
84
83
|
return
|
85
84
|
|
86
|
-
if not QCoreApplication.instance():
|
87
|
-
BECDispatcher.qapp = QCoreApplication([])
|
88
|
-
|
89
85
|
self._slots = collections.defaultdict(set)
|
90
86
|
self.client = client
|
91
87
|
|
File without changes
|
@@ -0,0 +1,127 @@
|
|
1
|
+
import re
|
2
|
+
|
3
|
+
from pydantic import Field, field_validator
|
4
|
+
from qtpy.QtWidgets import QTextEdit
|
5
|
+
|
6
|
+
from bec_widgets.utils.bec_connector import BECConnector, ConnectionConfig
|
7
|
+
from bec_widgets.utils.colors import Colors
|
8
|
+
|
9
|
+
|
10
|
+
class TextBoxConfig(ConnectionConfig):
|
11
|
+
|
12
|
+
theme: str = Field("dark", description="The theme of the figure widget.")
|
13
|
+
font_color: str = Field("#FFF", description="The font color of the text")
|
14
|
+
background_color: str = Field("#000", description="The background color of the widget.")
|
15
|
+
font_size: int = Field(16, description="The font size of the text in the widget.")
|
16
|
+
text: str = Field("", description="The text to display in the widget.")
|
17
|
+
|
18
|
+
@classmethod
|
19
|
+
@field_validator("theme")
|
20
|
+
def validate_theme(cls, v):
|
21
|
+
"""Validate the theme of the figure widget."""
|
22
|
+
if v not in ["dark", "light"]:
|
23
|
+
raise ValueError("Theme must be either 'dark' or 'light'")
|
24
|
+
return v
|
25
|
+
|
26
|
+
_validate_font_color = field_validator("font_color")(Colors.validate_color)
|
27
|
+
_validate_background_color = field_validator("background_color")(Colors.validate_color)
|
28
|
+
|
29
|
+
|
30
|
+
class TextBox(BECConnector, QTextEdit):
|
31
|
+
|
32
|
+
USER_ACCESS = ["set_color", "set_text", "set_font_size"]
|
33
|
+
|
34
|
+
def __init__(self, text: str = "", parent=None, client=None, config=None, gui_id=None):
|
35
|
+
if config is None:
|
36
|
+
config = TextBoxConfig(widget_class=self.__class__.__name__)
|
37
|
+
else:
|
38
|
+
if isinstance(config, dict):
|
39
|
+
config = TextBoxConfig(**config)
|
40
|
+
self.config = config
|
41
|
+
super().__init__(client=client, config=config, gui_id=gui_id)
|
42
|
+
QTextEdit.__init__(self, parent=parent)
|
43
|
+
|
44
|
+
self.config = config
|
45
|
+
self.setReadOnly(True)
|
46
|
+
self.setGeometry(self.rect())
|
47
|
+
self.set_color(self.config.background_color, self.config.font_color)
|
48
|
+
if not text:
|
49
|
+
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>"
|
50
|
+
self.set_text(text)
|
51
|
+
|
52
|
+
def change_theme(self) -> None:
|
53
|
+
"""
|
54
|
+
Change the theme of the figure widget.
|
55
|
+
"""
|
56
|
+
if self.config.theme == "dark":
|
57
|
+
theme = "light"
|
58
|
+
font_color = "#000"
|
59
|
+
background_color = "#FFF"
|
60
|
+
else:
|
61
|
+
theme = "dark"
|
62
|
+
font_color = "#FFF"
|
63
|
+
background_color = "#000"
|
64
|
+
self.config.theme = theme
|
65
|
+
self.set_color(background_color, font_color)
|
66
|
+
|
67
|
+
def set_color(self, background_color: str, font_color: str) -> None:
|
68
|
+
"""Set the background color of the widget.
|
69
|
+
|
70
|
+
Args:
|
71
|
+
background_color (str): The color to set the background in HEX.
|
72
|
+
font_color (str): The color to set the font in HEX.
|
73
|
+
|
74
|
+
"""
|
75
|
+
self.config.background_color = background_color
|
76
|
+
self.config.font_color = font_color
|
77
|
+
self._update_stylesheet()
|
78
|
+
|
79
|
+
def set_font_size(self, size: int) -> None:
|
80
|
+
"""Set the font size of the text in the widget.
|
81
|
+
|
82
|
+
Args:
|
83
|
+
size (int): The font size to set.
|
84
|
+
"""
|
85
|
+
self.config.font_size = size
|
86
|
+
self._update_stylesheet()
|
87
|
+
|
88
|
+
def _update_stylesheet(self):
|
89
|
+
"""Update the stylesheet of the widget."""
|
90
|
+
self.setStyleSheet(
|
91
|
+
f"background-color: {self.config.background_color}; color: {self.config.font_color}; font-size: {self.config.font_size}px"
|
92
|
+
)
|
93
|
+
|
94
|
+
def set_text(self, text: str) -> None:
|
95
|
+
"""Set the text of the widget.
|
96
|
+
|
97
|
+
Args:
|
98
|
+
text (str): The text to set.
|
99
|
+
"""
|
100
|
+
if self.is_html(text):
|
101
|
+
self.setHtml(text)
|
102
|
+
else:
|
103
|
+
self.setPlainText(text)
|
104
|
+
self.config.text = text
|
105
|
+
|
106
|
+
def is_html(self, text: str) -> bool:
|
107
|
+
"""Check if the text contains HTML tags.
|
108
|
+
|
109
|
+
Args:
|
110
|
+
text (str): The text to check.
|
111
|
+
|
112
|
+
Returns:
|
113
|
+
bool: True if the text contains HTML tags, False otherwise.
|
114
|
+
"""
|
115
|
+
return bool(re.search(r"<[a-zA-Z/][^>]*>", text))
|
116
|
+
|
117
|
+
|
118
|
+
if __name__ == "__main__":
|
119
|
+
import sys
|
120
|
+
|
121
|
+
from qtpy.QtWidgets import QApplication
|
122
|
+
|
123
|
+
app = QApplication(sys.argv)
|
124
|
+
|
125
|
+
widget = TextBox()
|
126
|
+
widget.show()
|
127
|
+
sys.exit(app.exec())
|
@@ -2,11 +2,11 @@
|
|
2
2
|
.gitlab-ci.yml,sha256=3PU2LONUl10zIOD4UCPQgA6vVBtniabww1MQkTMce7I,7995
|
3
3
|
.pylintrc,sha256=OstrgmEyP0smNFBKoIN5_26-UmNZgMHnbjvAWX0UrLs,18535
|
4
4
|
.readthedocs.yaml,sha256=aSOc277LqXcsTI6lgvm_JY80lMlr69GbPKgivua2cS0,603
|
5
|
-
CHANGELOG.md,sha256=
|
5
|
+
CHANGELOG.md,sha256=2guAV_VW9TfxaJIOGzjUg0l968nM8kwFLtXNUkop-jY,6936
|
6
6
|
LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
|
7
|
-
PKG-INFO,sha256=
|
7
|
+
PKG-INFO,sha256=MNvEiOQ0tVlzDg9MrZ4Jq1HjSIb0l2Lg14-2kN9IgqQ,1302
|
8
8
|
README.md,sha256=y4jB6wvArS7N8_iTbKWnSM_oRAqLA2GqgzUR-FMh5sU,2645
|
9
|
-
pyproject.toml,sha256=
|
9
|
+
pyproject.toml,sha256=oJZwUnqVMwN3MDK_gvSWFIVyGvKEidp-NyAgJwB_LUQ,2115
|
10
10
|
.git_hooks/pre-commit,sha256=n3RofIZHJl8zfJJIUomcMyYGFi_rwq4CC19z0snz3FI,286
|
11
11
|
.gitlab/issue_templates/bug_report_template.md,sha256=gAuyEwl7XlnebBrkiJ9AqffSNOywmr8vygUFWKTuQeI,386
|
12
12
|
.gitlab/issue_templates/documentation_update_template.md,sha256=FHLdb3TS_D9aL4CYZCjyXSulbaW5mrN2CmwTaeLPbNw,860
|
@@ -17,22 +17,22 @@ bec_widgets/assets/bec_widgets_icon.png,sha256=K8dgGwIjalDh9PRHUsSQBqgdX7a00nM3i
|
|
17
17
|
bec_widgets/assets/terminal_icon.png,sha256=bJl7Tft4Fi2uxvuXI8o14uMHnI9eAWKSU2uftXCH9ws,3889
|
18
18
|
bec_widgets/cli/__init__.py,sha256=d0Q6Fn44e7wFfLabDOBxpcJ1DPKWlFunGYDUBmO-4hA,22
|
19
19
|
bec_widgets/cli/auto_updates.py,sha256=DyBV3HnjMSH-cvVkYNcDiYKVf0Xut4Qy2qGQqkW47Bw,4833
|
20
|
-
bec_widgets/cli/client.py,sha256=
|
21
|
-
bec_widgets/cli/client_utils.py,sha256=
|
20
|
+
bec_widgets/cli/client.py,sha256=PkKPeR4KJjJP-DOPTq3yYJIuWlwXLHjTyZunCYSsU1I,55332
|
21
|
+
bec_widgets/cli/client_utils.py,sha256=tGfe3OKCDxgPHWKbJsM8myHooDwKNa-e5bjqGrtwwLg,10464
|
22
22
|
bec_widgets/cli/generate_cli.py,sha256=DIaGz7nhwef3ebIaP4LtiUC3q7MoM1swJ_e0SgAO2jo,6901
|
23
23
|
bec_widgets/cli/rpc_register.py,sha256=QxXUZu5XNg00Yf5O3UHWOXg3-f_pzKjjoZYMOa-MOJc,2216
|
24
|
-
bec_widgets/cli/rpc_wigdet_handler.py,sha256=
|
24
|
+
bec_widgets/cli/rpc_wigdet_handler.py,sha256=1oE2TSbwQdfLEaZiscyDX2eExHsenp2BF5Lwy8PE6LA,1118
|
25
25
|
bec_widgets/cli/server.py,sha256=rsj31Vsx6ayThNe4PQelQFahGjYXFZjfrNyB2fnm6Ro,5737
|
26
26
|
bec_widgets/examples/__init__.py,sha256=WWQ0cu7m8sA4Ehy-DWdTIqSISjaHsbxhsNmNrMnhDZU,202
|
27
27
|
bec_widgets/examples/jupyter_console/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
28
|
-
bec_widgets/examples/jupyter_console/jupyter_console_window.py,sha256=
|
28
|
+
bec_widgets/examples/jupyter_console/jupyter_console_window.py,sha256=lViKNC3TOh6_-SztcKaRnYkf-V_EQ9T1cuyORskVX6c,5339
|
29
29
|
bec_widgets/examples/jupyter_console/jupyter_console_window.ui,sha256=2A2mNTUMZBYygz8K4qWzrcjnNqZBMVyeHm26iLZVRWI,1473
|
30
30
|
bec_widgets/examples/motor_movement/__init__.py,sha256=LzPJkxLAxOsZCbXR-fRCPmeYobp7Yqds6tDxW4W1gSw,214
|
31
31
|
bec_widgets/examples/motor_movement/motor_control_compilations.py,sha256=8rpA7a2xVZTDMrx7YQIj3IJew78J1gcVMkHvloS0U_Q,9055
|
32
32
|
bec_widgets/examples/motor_movement/motor_controller.ui,sha256=83XX6NGILwntoUIghvzWnMuGf80O8khK3SduVKTAEFM,29105
|
33
33
|
bec_widgets/utils/__init__.py,sha256=B7OZ2ArjyFaGNh4XYIbk49agnYCz704ltuFSalLCjSA,481
|
34
34
|
bec_widgets/utils/bec_connector.py,sha256=RxHJNF7JjtY5pRbTMu2eQTiRXvoyJ53QuTYxHjZba38,5357
|
35
|
-
bec_widgets/utils/bec_dispatcher.py,sha256=
|
35
|
+
bec_widgets/utils/bec_dispatcher.py,sha256=nLdcj2u4dy8-ZR03XioCzr7hBg9Wq4Kw58OU6sDorT4,5593
|
36
36
|
bec_widgets/utils/bec_table.py,sha256=nA2b8ukSeUfquFMAxGrUVOqdrzMoDYD6O_4EYbOG2zk,717
|
37
37
|
bec_widgets/utils/colors.py,sha256=GYSDe0ZxsJSwxvuy-yG2BH17qlf_Sjq8dhDcyp9IhBI,8532
|
38
38
|
bec_widgets/utils/container_utils.py,sha256=m3VUyAYmSWkEwApP9tBvKxPYVtc2kHw4toxIpMryJy4,1495
|
@@ -87,6 +87,8 @@ bec_widgets/widgets/scan_control/scan_control.py,sha256=B5n2U2iVtTCY3Tx93JyBqzGC
|
|
87
87
|
bec_widgets/widgets/spiral_progress_bar/__init__.py,sha256=4efbtcqCToMIw5bkQrTzy2TzuBCXvlhuUPh1bYC_Yzg,51
|
88
88
|
bec_widgets/widgets/spiral_progress_bar/ring.py,sha256=7i5oKpW8eUQGvLyKce2-2rlaGDVLec__DoWp6hfJlRw,10524
|
89
89
|
bec_widgets/widgets/spiral_progress_bar/spiral_progress_bar.py,sha256=cMi4g7zNTLrkkzZ9ChiIBTaqigDCYwzrgA2iRmq9dUI,24050
|
90
|
+
bec_widgets/widgets/text_box/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
91
|
+
bec_widgets/widgets/text_box/text_box.py,sha256=kykQ_Zcxh8IGcPEP5-oGGQwoZEpY9vhxRIM8TY8kTYg,4240
|
90
92
|
bec_widgets/widgets/toolbar/__init__.py,sha256=d-TP4_cr_VbpwreMM4ePnfZ5YXsEPQ45ibEf75nuGoE,36
|
91
93
|
bec_widgets/widgets/toolbar/toolbar.py,sha256=e0zCD_0q7K4NVhrzD8001Qvfxt-VhqHTgofchS9NgCM,5125
|
92
94
|
bec_widgets/widgets/website/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -127,9 +129,10 @@ docs/user/widgets/motor.gif,sha256=FtaWdRHx4UZaGJPpq8LNhMMgX4PFcAB6IZ93JCMEh_w,2
|
|
127
129
|
docs/user/widgets/progress_bar.gif,sha256=5jh0Zw2BBGPuNxszV1DBLJCb4_6glIRX-U2ABjnsK2k,5263592
|
128
130
|
docs/user/widgets/scatter_2D.gif,sha256=yHpsuAUseMafJjI_J5BcOhmE3nu9VFn_Xm9XHzJaH5I,13188862
|
129
131
|
docs/user/widgets/spiral_progress_bar.md,sha256=QTgUDIl6XPuK_HwSfB6sNijZ4bss26biDg6B_mJ8Pxk,2208
|
132
|
+
docs/user/widgets/text_box.md,sha256=i40AxKJP0PxrYW0x0up1VIovPFvemsaZoosGjOn4iZE,931
|
130
133
|
docs/user/widgets/w1D.gif,sha256=tuHbleJpl6bJFNNC2OdndF5LF7IyfvlkFCMGZajrQPs,622773
|
131
134
|
docs/user/widgets/website.md,sha256=wfudAupdtHX-Sfritg0xMWXZLLczJ4XwMLNWvu6ww-w,705
|
132
|
-
docs/user/widgets/widgets.md,sha256=
|
135
|
+
docs/user/widgets/widgets.md,sha256=NzRfrgd4LWmZHa2Cs_1G59LeY5uAlFdy5aP00AtGAjk,380
|
133
136
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
134
137
|
tests/end-2-end/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
135
138
|
tests/end-2-end/conftest.py,sha256=taLqiYVzOhJjMre5ypgQjB7wzSXP4soKANW3XfAjems,1773
|
@@ -154,6 +157,7 @@ tests/unit_tests/test_rpc_register.py,sha256=hECjZEimd440mwRrO0rg7L3PKN7__3DgjmE
|
|
154
157
|
tests/unit_tests/test_scan_control.py,sha256=7dtGpE0g4FqUhhQeCkyJl-9o7NH3DFZJgEaqDmBYbBc,7551
|
155
158
|
tests/unit_tests/test_spiral_progress_bar.py,sha256=yak3N9-TmEM3lQZPSROL4cAx9mior__se1XADlMScks,12418
|
156
159
|
tests/unit_tests/test_stop_button.py,sha256=hOoWO0emkvd5bR_EExxCnKsiZgXKqf_uIGTwzWLxhDw,704
|
160
|
+
tests/unit_tests/test_text_box_widget.py,sha256=cT0uEHt_6d-FwST0A_wE9sFW9E3F_nJbKhuBAeU4yHg,1862
|
157
161
|
tests/unit_tests/test_waveform1d.py,sha256=j9-CCE0BkFVI3Gnv8pjV1gc9HwA5PYG0_ox1oZ60F6w,15272
|
158
162
|
tests/unit_tests/test_website_widget.py,sha256=fBADIJJBAHU4Ro7u95kdemFVNv196UOcuO9oLHuHt8A,761
|
159
163
|
tests/unit_tests/test_widget_io.py,sha256=FeL3ZYSBQnRt6jxj8VGYw1cmcicRQyHKleahw7XIyR0,3475
|
@@ -163,8 +167,8 @@ tests/unit_tests/test_configs/config_device_no_entry.yaml,sha256=hdvue9KLc_kfNzG
|
|
163
167
|
tests/unit_tests/test_configs/config_scan.yaml,sha256=vo484BbWOjA_e-h6bTjSV9k7QaQHrlAvx-z8wtY-P4E,1915
|
164
168
|
tests/unit_tests/test_msgs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
165
169
|
tests/unit_tests/test_msgs/available_scans_message.py,sha256=m_z97hIrjHXXMa2Ex-UvsPmTxOYXfjxyJaGkIY6StTY,46532
|
166
|
-
bec_widgets-0.
|
167
|
-
bec_widgets-0.
|
168
|
-
bec_widgets-0.
|
169
|
-
bec_widgets-0.
|
170
|
-
bec_widgets-0.
|
170
|
+
bec_widgets-0.63.1.dist-info/METADATA,sha256=MNvEiOQ0tVlzDg9MrZ4Jq1HjSIb0l2Lg14-2kN9IgqQ,1302
|
171
|
+
bec_widgets-0.63.1.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
|
172
|
+
bec_widgets-0.63.1.dist-info/entry_points.txt,sha256=80s2YKCNziN2ROUYbpDRyEmiejMf_dshmiYCdN7qNsU,70
|
173
|
+
bec_widgets-0.63.1.dist-info/licenses/LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
|
174
|
+
bec_widgets-0.63.1.dist-info/RECORD,,
|
@@ -0,0 +1,33 @@
|
|
1
|
+
(user.widgets.text_box)=
|
2
|
+
# [Text Box Widget](/api_reference/_autosummary/bec_widgets.cli.client.TextBoxWidget)
|
3
|
+
**Purpose:**
|
4
|
+
|
5
|
+
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.
|
6
|
+
|
7
|
+
**Key Features:**
|
8
|
+
|
9
|
+
- set the text to display.
|
10
|
+
- automatically detects if the text is plain text or HTML text.
|
11
|
+
- set background color and font color.
|
12
|
+
|
13
|
+
**Code example:**
|
14
|
+
|
15
|
+
The following code snipped demonstrates how to create a `TextBox` widget using BEC Widgets within BEC.
|
16
|
+
```python
|
17
|
+
text_box = gui.add_dock().add_widget("TextBox")
|
18
|
+
# set the text to display
|
19
|
+
text_box.set_text("Hello, World!")
|
20
|
+
# set the background color and font color
|
21
|
+
text_box.set_color(backgroud_color="#FFF", font_color="#000")
|
22
|
+
# set the text to display as HTML
|
23
|
+
text_box.set_text("<h1>Welcome to BEC Widgets</h1><p>This is an example of displaying <strong>HTML</strong> text.</p>")
|
24
|
+
```
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
|
32
|
+
|
33
|
+
|
docs/user/widgets/widgets.md
CHANGED
pyproject.toml
CHANGED
@@ -0,0 +1,55 @@
|
|
1
|
+
import re
|
2
|
+
from unittest import mock
|
3
|
+
|
4
|
+
import pytest
|
5
|
+
|
6
|
+
from bec_widgets.widgets.text_box.text_box import TextBox
|
7
|
+
|
8
|
+
from .client_mocks import mocked_client
|
9
|
+
|
10
|
+
|
11
|
+
@pytest.fixture
|
12
|
+
def text_box_widget(qtbot, mocked_client):
|
13
|
+
widget = TextBox(client=mocked_client)
|
14
|
+
qtbot.addWidget(widget)
|
15
|
+
qtbot.waitExposed(widget)
|
16
|
+
yield widget
|
17
|
+
widget.close()
|
18
|
+
|
19
|
+
|
20
|
+
def test_textbox_widget(text_box_widget):
|
21
|
+
"""Test the TextBox widget."""
|
22
|
+
text = "Hello World!"
|
23
|
+
text_box_widget.set_text(text)
|
24
|
+
assert text_box_widget.toPlainText() == text
|
25
|
+
|
26
|
+
text_box_widget.set_color("#FFDDC1", "#123456")
|
27
|
+
text_box_widget.set_font_size(20)
|
28
|
+
assert (
|
29
|
+
text_box_widget.styleSheet() == "background-color: #FFDDC1; color: #123456; font-size: 20px"
|
30
|
+
)
|
31
|
+
text_box_widget.set_color("white", "blue")
|
32
|
+
text_box_widget.set_font_size(14)
|
33
|
+
assert text_box_widget.styleSheet() == "background-color: white; color: blue; font-size: 14px"
|
34
|
+
text = "<h1>Welcome to PyQt6</h1><p>This is an example of displaying <strong>HTML</strong> text.</p>"
|
35
|
+
with mock.patch.object(text_box_widget, "setHtml") as mocked_set_html:
|
36
|
+
text_box_widget.set_text(text)
|
37
|
+
assert mocked_set_html.call_count == 1
|
38
|
+
assert mocked_set_html.call_args == mock.call(text)
|
39
|
+
|
40
|
+
|
41
|
+
def test_textbox_change_theme(text_box_widget):
|
42
|
+
"""Test change theme functionaility"""
|
43
|
+
# Default is dark theme
|
44
|
+
text_box_widget.change_theme()
|
45
|
+
assert text_box_widget.config.theme == "light"
|
46
|
+
assert (
|
47
|
+
text_box_widget.styleSheet()
|
48
|
+
== f"background-color: #FFF; color: #000; font-size: {text_box_widget.config.font_size}px"
|
49
|
+
)
|
50
|
+
text_box_widget.change_theme()
|
51
|
+
assert text_box_widget.config.theme == "dark"
|
52
|
+
assert (
|
53
|
+
text_box_widget.styleSheet()
|
54
|
+
== f"background-color: #000; color: #FFF; font-size: {text_box_widget.config.font_size}px"
|
55
|
+
)
|
File without changes
|
File without changes
|
File without changes
|