bec-widgets 0.60.0__py3-none-any.whl → 0.62.0__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 +22 -17
- PKG-INFO +1 -1
- bec_widgets/cli/auto_updates.py +5 -3
- bec_widgets/cli/client_utils.py +62 -34
- bec_widgets/examples/jupyter_console/jupyter_console_window.py +6 -5
- bec_widgets/utils/bec_dispatcher.py +5 -1
- bec_widgets/widgets/__init__.py +1 -0
- bec_widgets/widgets/buttons/__init__.py +1 -0
- bec_widgets/widgets/buttons/stop_button/__init__.py +0 -0
- bec_widgets/widgets/buttons/stop_button/stop_button.py +32 -0
- {bec_widgets-0.60.0.dist-info → bec_widgets-0.62.0.dist-info}/METADATA +1 -1
- {bec_widgets-0.60.0.dist-info → bec_widgets-0.62.0.dist-info}/RECORD +21 -16
- docs/user/customisation.md +111 -1
- docs/user/widgets/buttons.md +38 -0
- docs/user/widgets/widgets.md +1 -0
- pyproject.toml +1 -1
- tests/end-2-end/test_bec_dock_rpc_e2e.py +16 -4
- tests/unit_tests/test_stop_button.py +25 -0
- {bec_widgets-0.60.0.dist-info → bec_widgets-0.62.0.dist-info}/WHEEL +0 -0
- {bec_widgets-0.60.0.dist-info → bec_widgets-0.62.0.dist-info}/entry_points.txt +0 -0
- {bec_widgets-0.60.0.dist-info → bec_widgets-0.62.0.dist-info}/licenses/LICENSE +0 -0
CHANGELOG.md
CHANGED
@@ -2,6 +2,28 @@
|
|
2
2
|
|
3
3
|
|
4
4
|
|
5
|
+
## v0.62.0 (2024-06-12)
|
6
|
+
|
7
|
+
### Feature
|
8
|
+
|
9
|
+
* feat: implement non-polling, interruptible waiting of gui instruction response with timeout ([`abc6caa`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/abc6caa2d0b6141dfbe1f3d025f78ae14deddcb3))
|
10
|
+
|
11
|
+
### Unknown
|
12
|
+
|
13
|
+
* doc: add documentation about creating custom GUI applications embedding BEC Widgets ([`17a0068`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/17a00687579f5efab1990cd83862ec0e78198633))
|
14
|
+
|
15
|
+
|
16
|
+
## v0.61.0 (2024-06-12)
|
17
|
+
|
18
|
+
### Feature
|
19
|
+
|
20
|
+
* feat(widgets/stop_button): General stop button added ([`61ba08d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/61ba08d0b8df9f48f5c54c7c2b4e6d395206e7e6))
|
21
|
+
|
22
|
+
### Refactor
|
23
|
+
|
24
|
+
* refactor: improve labe of auto_update script ([`40b5688`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/40b568815893cd41af3531bb2e647ca1e2e315f4))
|
25
|
+
|
26
|
+
|
5
27
|
## v0.60.0 (2024-06-08)
|
6
28
|
|
7
29
|
### Ci
|
@@ -141,20 +163,3 @@
|
|
141
163
|
### Fix
|
142
164
|
|
143
165
|
* fix(docks): set_title do update dock internal _name now ([`15cbc21`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/15cbc21e5bb3cf85f5822d44a2b3665b5aa2f346))
|
144
|
-
|
145
|
-
* fix(docks): docks widget_list adn dockarea panels return values fixed ([`ffae5ee`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/ffae5ee54e6b43da660131092452adff195ba4fb))
|
146
|
-
|
147
|
-
|
148
|
-
## v0.57.3 (2024-06-06)
|
149
|
-
|
150
|
-
### Documentation
|
151
|
-
|
152
|
-
* docs(bar): docs updated ([`4be0d14`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4be0d14b7445c2322c2aef86257db168a841265c))
|
153
|
-
|
154
|
-
### Fix
|
155
|
-
|
156
|
-
* 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))
|
157
|
-
|
158
|
-
* fix(ring): enable_auto_updates(True) do not reset properties of already setup bars ([`a2abad3`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a2abad344f4c0039516eb60a825afb6822c5b19a))
|
159
|
-
|
160
|
-
* fix(ring): set_min_max accepts floats ([`d44b1cf`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/d44b1cf8b107cf02deedd9154b77d01c7f9ed05d))
|
PKG-INFO
CHANGED
bec_widgets/cli/auto_updates.py
CHANGED
@@ -118,7 +118,7 @@ class AutoUpdates:
|
|
118
118
|
if not dev_y:
|
119
119
|
return
|
120
120
|
fig.clear_all()
|
121
|
-
plt = fig.plot(x_name=dev_x, y_name=dev_y)
|
121
|
+
plt = fig.plot(x_name=dev_x, y_name=dev_y, label=f"Scan {info.scan_number} - {dev_y}")
|
122
122
|
plt.set(title=f"Scan {info.scan_number}", x_label=dev_x, y_label=dev_y)
|
123
123
|
|
124
124
|
def simple_grid_scan(self, info: ScanInfo) -> None:
|
@@ -132,7 +132,9 @@ class AutoUpdates:
|
|
132
132
|
dev_y = info.scan_report_devices[1]
|
133
133
|
dev_z = self.get_selected_device(info.monitored_devices, self.gui.selected_device)
|
134
134
|
fig.clear_all()
|
135
|
-
plt = fig.plot(
|
135
|
+
plt = fig.plot(
|
136
|
+
x_name=dev_x, y_name=dev_y, z_name=dev_z, label=f"Scan {info.scan_number} - {dev_z}"
|
137
|
+
)
|
136
138
|
plt.set(title=f"Scan {info.scan_number}", x_label=dev_x, y_label=dev_y)
|
137
139
|
|
138
140
|
def best_effort(self, info: ScanInfo) -> None:
|
@@ -147,5 +149,5 @@ class AutoUpdates:
|
|
147
149
|
if not dev_y:
|
148
150
|
return
|
149
151
|
fig.clear_all()
|
150
|
-
plt = fig.plot(x_name=dev_x, y_name=dev_y, label=f"Scan {info.scan_number}")
|
152
|
+
plt = fig.plot(x_name=dev_x, y_name=dev_y, label=f"Scan {info.scan_number} - {dev_y}")
|
151
153
|
plt.set(title=f"Scan {info.scan_number}", x_label=dev_x, y_label=dev_y)
|
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 QEventLoop, QSocketNotifier, QTimer
|
18
18
|
|
19
19
|
import bec_widgets.cli.client as client
|
20
20
|
from bec_widgets.cli.auto_updates import AutoUpdates
|
@@ -24,6 +24,8 @@ 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
|
+
|
27
29
|
messages = lazy_import("bec_lib.messages")
|
28
30
|
# from bec_lib.connector import MessageObject
|
29
31
|
MessageObject = lazy_import_from("bec_lib.connector", ("MessageObject",))
|
@@ -205,6 +207,48 @@ class RPCResponseTimeoutError(Exception):
|
|
205
207
|
)
|
206
208
|
|
207
209
|
|
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
|
+
|
208
252
|
class RPCBase:
|
209
253
|
def __init__(self, gui_id: str = None, config: dict = None, parent=None) -> None:
|
210
254
|
self._client = BECDispatcher().client
|
@@ -231,7 +275,7 @@ class RPCBase:
|
|
231
275
|
parent = parent._parent
|
232
276
|
return parent
|
233
277
|
|
234
|
-
def _run_rpc(self, method, *args, wait_for_rpc_response=True, **kwargs):
|
278
|
+
def _run_rpc(self, method, *args, wait_for_rpc_response=True, timeout=3, **kwargs):
|
235
279
|
"""
|
236
280
|
Run the RPC call.
|
237
281
|
|
@@ -253,16 +297,24 @@ class RPCBase:
|
|
253
297
|
|
254
298
|
# pylint: disable=protected-access
|
255
299
|
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
|
+
|
256
305
|
self._client.connector.set_and_publish(MessageEndpoints.gui_instructions(receiver), rpc_msg)
|
257
306
|
|
258
|
-
if
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
307
|
+
if wait_for_rpc_response:
|
308
|
+
response = redis_msg.wait(timeout)
|
309
|
+
|
310
|
+
if response is None:
|
311
|
+
raise RPCResponseTimeoutError(request_id, timeout)
|
312
|
+
|
313
|
+
# get class name
|
314
|
+
if not response.accepted:
|
315
|
+
raise ValueError(response.message["error"])
|
316
|
+
msg_result = response.message.get("result")
|
317
|
+
return self._create_widget_from_msg_result(msg_result)
|
266
318
|
|
267
319
|
def _create_widget_from_msg_result(self, msg_result):
|
268
320
|
if msg_result is None:
|
@@ -285,30 +337,6 @@ class RPCBase:
|
|
285
337
|
return cls(parent=self, **msg_result)
|
286
338
|
return msg_result
|
287
339
|
|
288
|
-
def _wait_for_response(self, request_id: str, timeout: int = 5):
|
289
|
-
"""
|
290
|
-
Wait for the response from the server.
|
291
|
-
|
292
|
-
Args:
|
293
|
-
request_id(str): The request ID.
|
294
|
-
timeout(int): The timeout in seconds.
|
295
|
-
|
296
|
-
Returns:
|
297
|
-
The response from the server.
|
298
|
-
"""
|
299
|
-
start_time = time.time()
|
300
|
-
response = None
|
301
|
-
|
302
|
-
while response is None and self.gui_is_alive() and (time.time() - start_time) < timeout:
|
303
|
-
response = self._client.connector.get(
|
304
|
-
MessageEndpoints.gui_instruction_response(request_id)
|
305
|
-
)
|
306
|
-
QCoreApplication.processEvents() # keep UI responsive (and execute signals/slots)
|
307
|
-
if response is None and (time.time() - start_time) >= timeout:
|
308
|
-
raise RPCResponseTimeoutError(request_id, timeout)
|
309
|
-
|
310
|
-
return response
|
311
|
-
|
312
340
|
def gui_is_alive(self):
|
313
341
|
"""
|
314
342
|
Check if the GUI is alive.
|
@@ -136,17 +136,18 @@ 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
|
-
|
143
139
|
app = QApplication(sys.argv)
|
144
140
|
app.setApplicationName("Jupyter Console")
|
145
141
|
app.setApplicationDisplayName("Jupyter Console")
|
146
|
-
|
142
|
+
qdarktheme.setup_theme("auto")
|
147
143
|
icon = QIcon()
|
148
144
|
icon.addFile(os.path.join(module_path, "assets", "terminal_icon.png"), size=QSize(48, 48))
|
149
145
|
app.setWindowIcon(icon)
|
146
|
+
|
147
|
+
bec_dispatcher = BECDispatcher()
|
148
|
+
client = bec_dispatcher.client
|
149
|
+
client.start()
|
150
|
+
|
150
151
|
win = JupyterConsoleWindow()
|
151
152
|
win.show()
|
152
153
|
|
@@ -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 QObject
|
12
|
+
from qtpy.QtCore import QCoreApplication, QObject
|
13
13
|
from qtpy.QtCore import Signal as pyqtSignal
|
14
14
|
|
15
15
|
if TYPE_CHECKING:
|
@@ -71,6 +71,7 @@ class BECDispatcher:
|
|
71
71
|
|
72
72
|
_instance = None
|
73
73
|
_initialized = False
|
74
|
+
qapp = None
|
74
75
|
|
75
76
|
def __new__(cls, client=None, config: str = None, *args, **kwargs):
|
76
77
|
if cls._instance is None:
|
@@ -82,6 +83,9 @@ class BECDispatcher:
|
|
82
83
|
if self._initialized:
|
83
84
|
return
|
84
85
|
|
86
|
+
if not QCoreApplication.instance():
|
87
|
+
BECDispatcher.qapp = QCoreApplication([])
|
88
|
+
|
85
89
|
self._slots = collections.defaultdict(set)
|
86
90
|
self.client = client
|
87
91
|
|
bec_widgets/widgets/__init__.py
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
from .stop_button.stop_button import StopButton
|
File without changes
|
@@ -0,0 +1,32 @@
|
|
1
|
+
from qtpy.QtWidgets import QPushButton
|
2
|
+
|
3
|
+
from bec_widgets.utils import BECConnector
|
4
|
+
|
5
|
+
|
6
|
+
class StopButton(BECConnector, QPushButton):
|
7
|
+
"""A button that stops the current scan."""
|
8
|
+
|
9
|
+
def __init__(self, parent=None, client=None, config=None, gui_id=None):
|
10
|
+
super().__init__(client=client, config=config, gui_id=gui_id)
|
11
|
+
QPushButton.__init__(self, parent=parent)
|
12
|
+
|
13
|
+
self.get_bec_shortcuts()
|
14
|
+
self.setText("Stop")
|
15
|
+
self.setStyleSheet("background-color: #cc181e; color: white")
|
16
|
+
self.clicked.connect(self.stop_scan)
|
17
|
+
|
18
|
+
def stop_scan(self):
|
19
|
+
"""Stop the scan."""
|
20
|
+
self.queue.request_scan_abortion()
|
21
|
+
self.queue.request_queue_reset()
|
22
|
+
|
23
|
+
|
24
|
+
if __name__ == "__main__": # pragma: no cover
|
25
|
+
import sys
|
26
|
+
|
27
|
+
from qtpy.QtWidgets import QApplication
|
28
|
+
|
29
|
+
app = QApplication(sys.argv)
|
30
|
+
widget = StopButton()
|
31
|
+
widget.show()
|
32
|
+
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=TUs5Jd_fGHQ6IUvMRmlJlJZTW3dBC8hTsnJ-D8_lzdo,6881
|
6
6
|
LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
|
7
|
-
PKG-INFO,sha256=
|
7
|
+
PKG-INFO,sha256=QoubvfbHPjKGZLAQaC7bj2h7AdPFa8rk6Jj4dMgQ--o,1302
|
8
8
|
README.md,sha256=y4jB6wvArS7N8_iTbKWnSM_oRAqLA2GqgzUR-FMh5sU,2645
|
9
|
-
pyproject.toml,sha256=
|
9
|
+
pyproject.toml,sha256=p5YYYOClz-NdVPtwURPrkDjO6EJmjEOt8H-axu7tyvc,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
|
@@ -16,23 +16,23 @@ bec_widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
bec_widgets/assets/bec_widgets_icon.png,sha256=K8dgGwIjalDh9PRHUsSQBqgdX7a00nM3igZdc20pkYM,1747017
|
17
17
|
bec_widgets/assets/terminal_icon.png,sha256=bJl7Tft4Fi2uxvuXI8o14uMHnI9eAWKSU2uftXCH9ws,3889
|
18
18
|
bec_widgets/cli/__init__.py,sha256=d0Q6Fn44e7wFfLabDOBxpcJ1DPKWlFunGYDUBmO-4hA,22
|
19
|
-
bec_widgets/cli/auto_updates.py,sha256=
|
19
|
+
bec_widgets/cli/auto_updates.py,sha256=DyBV3HnjMSH-cvVkYNcDiYKVf0Xut4Qy2qGQqkW47Bw,4833
|
20
20
|
bec_widgets/cli/client.py,sha256=6mzmRe_pjuz-3CoBJZkxaa05VzAmPLi_cDJ3uljmw8Y,54363
|
21
|
-
bec_widgets/cli/client_utils.py,sha256=
|
21
|
+
bec_widgets/cli/client_utils.py,sha256=z85Q0QWtBUf4A1X3ovWgOExKI8qVzIR2hE-lPWEOdpY,11645
|
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
24
|
bec_widgets/cli/rpc_wigdet_handler.py,sha256=VRLujiICU_Gq0mO1Hr15YwjlxU7OcAzqeoM8QHqSubM,1032
|
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=AKIlwcOMBJ8UAIBEhPipc9lDwA25UmmBDEYAfC0L7kU,5338
|
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=3zzf7LXCWtxiroUuSdObGexuYfRoq0QY1i3hJ7GmbBE,5726
|
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
|
@@ -46,7 +46,10 @@ bec_widgets/utils/ui_loader.py,sha256=5NktcP1r1HQub7K82fW_jkj8rT2cqJQdMvDxwToLY4
|
|
46
46
|
bec_widgets/utils/validator_delegate.py,sha256=Emj1WF6W8Ke1ruBWUfmHdVJpmOSPezuOt4zvQTay_44,442
|
47
47
|
bec_widgets/utils/widget_io.py,sha256=f36198CvT_EzWQ_cg2G-4tRRsaMdJ3yVqsZWKJCQEfA,10880
|
48
48
|
bec_widgets/utils/yaml_dialog.py,sha256=cMVif-39SB9WjwGH5FWBJcFs4tnfFJFs5cacydRyhy0,1853
|
49
|
-
bec_widgets/widgets/__init__.py,sha256=
|
49
|
+
bec_widgets/widgets/__init__.py,sha256=2GykeIg8_dqly6ZYny8HC7OGNpmafXXV3WN61vYGvzk,204
|
50
|
+
bec_widgets/widgets/buttons/__init__.py,sha256=74ucIRU6-anoqQ-zT7wbrysmxhg_3_04xGhN_kllNUI,48
|
51
|
+
bec_widgets/widgets/buttons/stop_button/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
52
|
+
bec_widgets/widgets/buttons/stop_button/stop_button.py,sha256=x4a7RvlMkHzOd05zKOGYkyTmBza7Me7jgOL9WIgA_c4,906
|
50
53
|
bec_widgets/widgets/dock/__init__.py,sha256=B7foHt02gnhM7mFksa7GJVwT7n0j_JvYDCt6wc6XR5g,61
|
51
54
|
bec_widgets/widgets/dock/dock.py,sha256=WPanKj6nodVP6PiEFkRTCj5Lf0pUupmM7i-5qZA_ATY,7596
|
52
55
|
bec_widgets/widgets/dock/dock_area.py,sha256=9c_tLzyBRllLfc4H5o9-4bvasWp5hWe1NWg4mupXVtU,7911
|
@@ -106,7 +109,7 @@ docs/assets/index_user_guide.svg,sha256=sRjKwOHVJStBYIQUFVcnfmbeXd2qAp0HYjleSp66
|
|
106
109
|
docs/assets/rocket_launch_48dp.svg,sha256=pdrPrBcKWUa5OlgWKM0B6TA6qAW7E57d7C7YW2r1OT8,1070
|
107
110
|
docs/developer/developer.md,sha256=7Z6sfkk_7BgwZ2vaX4z5_cJrs0miyeAYSGpqMbyBmOI,415
|
108
111
|
docs/introduction/introduction.md,sha256=wp7jmhkUtJnSnEnmIAZGUcau_3-5e5-FohvZb63khw4,1432
|
109
|
-
docs/user/customisation.md,sha256=
|
112
|
+
docs/user/customisation.md,sha256=MXqbljqokDXF3VhCeia1PITZe1mx1J3FfLTJ66TlaUA,3172
|
110
113
|
docs/user/user.md,sha256=uCTcjclIi6rdjYRQebko6bWFEVsjyfshsVU3BDYrC-Y,1403
|
111
114
|
docs/user/api_reference/api_reference.md,sha256=q2Imc48Rq6GcAP0R4bS3KuW5ptZZdsV4wxGJb3JJQHg,174
|
112
115
|
docs/user/applications/applications.md,sha256=yOECfaYRUEDIxF-O0duOwSJlG4f93RylrpMjbw1-8Dg,100
|
@@ -118,6 +121,7 @@ docs/user/getting_started/installation.md,sha256=nBl2Hfvo6ua3-tVZn1B-UG0hCTlrFY6
|
|
118
121
|
docs/user/getting_started/quick_start.md,sha256=VGU880GwamcIZcBE8tjxuqX2syE-71jqZedtskCoBbA,9405
|
119
122
|
docs/user/widgets/BECFigure.png,sha256=8dQr4u0uk_y0VV-R1Jh9yTR3Vidd9HDEno_07R0swaE,1605920
|
120
123
|
docs/user/widgets/bec_figure.md,sha256=BwcumbhZd6a2zKmoHTvwKr8kG8WxBx9lS_QwxNiBMpQ,5155
|
124
|
+
docs/user/widgets/buttons.md,sha256=SKxhLsu_gquNSG-zMAzVzz2d6bUwtZ9y259ILoMyCu0,1214
|
121
125
|
docs/user/widgets/image_plot.gif,sha256=_mVFhMTXGqwDOcEtrBHMZj5Thn2sLhDAHEeL2XyHN-s,14098977
|
122
126
|
docs/user/widgets/motor.gif,sha256=FtaWdRHx4UZaGJPpq8LNhMMgX4PFcAB6IZ93JCMEh_w,2280719
|
123
127
|
docs/user/widgets/progress_bar.gif,sha256=5jh0Zw2BBGPuNxszV1DBLJCb4_6glIRX-U2ABjnsK2k,5263592
|
@@ -125,11 +129,11 @@ docs/user/widgets/scatter_2D.gif,sha256=yHpsuAUseMafJjI_J5BcOhmE3nu9VFn_Xm9XHzJa
|
|
125
129
|
docs/user/widgets/spiral_progress_bar.md,sha256=QTgUDIl6XPuK_HwSfB6sNijZ4bss26biDg6B_mJ8Pxk,2208
|
126
130
|
docs/user/widgets/w1D.gif,sha256=tuHbleJpl6bJFNNC2OdndF5LF7IyfvlkFCMGZajrQPs,622773
|
127
131
|
docs/user/widgets/website.md,sha256=wfudAupdtHX-Sfritg0xMWXZLLczJ4XwMLNWvu6ww-w,705
|
128
|
-
docs/user/widgets/widgets.md,sha256=
|
132
|
+
docs/user/widgets/widgets.md,sha256=EfrElcZVh9jHt_0kIXdDtc7z3ErYGqwmVMxC_AHSGU0,370
|
129
133
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
130
134
|
tests/end-2-end/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
131
135
|
tests/end-2-end/conftest.py,sha256=taLqiYVzOhJjMre5ypgQjB7wzSXP4soKANW3XfAjems,1773
|
132
|
-
tests/end-2-end/test_bec_dock_rpc_e2e.py,sha256=
|
136
|
+
tests/end-2-end/test_bec_dock_rpc_e2e.py,sha256=8iJz4lITspY7eHdSgy9YvGUGTu3fsSperoVGBvTGT0U,9067
|
133
137
|
tests/end-2-end/test_bec_figure_rpc_e2e.py,sha256=zTbB_F4Fs-QG8KhMK24xfsrCQBgZUAguMk3KFdEdP2o,5095
|
134
138
|
tests/end-2-end/test_rpc_register_e2e.py,sha256=3dfCnSvdcRO92pzHt9WlCTK0vzTKAvPtliEoEKrtuzQ,1604
|
135
139
|
tests/unit_tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -149,6 +153,7 @@ tests/unit_tests/test_plot_base.py,sha256=bOdlgAxh9oKk5PwiQ_MSFmzr44uJ61Tlg242RC
|
|
149
153
|
tests/unit_tests/test_rpc_register.py,sha256=hECjZEimd440mwRrO0rg7L3PKN7__3DgjmESN6wx3bo,1179
|
150
154
|
tests/unit_tests/test_scan_control.py,sha256=7dtGpE0g4FqUhhQeCkyJl-9o7NH3DFZJgEaqDmBYbBc,7551
|
151
155
|
tests/unit_tests/test_spiral_progress_bar.py,sha256=yak3N9-TmEM3lQZPSROL4cAx9mior__se1XADlMScks,12418
|
156
|
+
tests/unit_tests/test_stop_button.py,sha256=hOoWO0emkvd5bR_EExxCnKsiZgXKqf_uIGTwzWLxhDw,704
|
152
157
|
tests/unit_tests/test_waveform1d.py,sha256=j9-CCE0BkFVI3Gnv8pjV1gc9HwA5PYG0_ox1oZ60F6w,15272
|
153
158
|
tests/unit_tests/test_website_widget.py,sha256=fBADIJJBAHU4Ro7u95kdemFVNv196UOcuO9oLHuHt8A,761
|
154
159
|
tests/unit_tests/test_widget_io.py,sha256=FeL3ZYSBQnRt6jxj8VGYw1cmcicRQyHKleahw7XIyR0,3475
|
@@ -158,8 +163,8 @@ tests/unit_tests/test_configs/config_device_no_entry.yaml,sha256=hdvue9KLc_kfNzG
|
|
158
163
|
tests/unit_tests/test_configs/config_scan.yaml,sha256=vo484BbWOjA_e-h6bTjSV9k7QaQHrlAvx-z8wtY-P4E,1915
|
159
164
|
tests/unit_tests/test_msgs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
160
165
|
tests/unit_tests/test_msgs/available_scans_message.py,sha256=m_z97hIrjHXXMa2Ex-UvsPmTxOYXfjxyJaGkIY6StTY,46532
|
161
|
-
bec_widgets-0.
|
162
|
-
bec_widgets-0.
|
163
|
-
bec_widgets-0.
|
164
|
-
bec_widgets-0.
|
165
|
-
bec_widgets-0.
|
166
|
+
bec_widgets-0.62.0.dist-info/METADATA,sha256=QoubvfbHPjKGZLAQaC7bj2h7AdPFa8rk6Jj4dMgQ--o,1302
|
167
|
+
bec_widgets-0.62.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
|
168
|
+
bec_widgets-0.62.0.dist-info/entry_points.txt,sha256=80s2YKCNziN2ROUYbpDRyEmiejMf_dshmiYCdN7qNsU,70
|
169
|
+
bec_widgets-0.62.0.dist-info/licenses/LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
|
170
|
+
bec_widgets-0.62.0.dist-info/RECORD,,
|
docs/user/customisation.md
CHANGED
@@ -1,8 +1,118 @@
|
|
1
1
|
(user.customisation)=
|
2
2
|
# Customisation
|
3
3
|
|
4
|
-
BEC Widgets
|
4
|
+
## Leveraging BEC Widgets in custom GUI applications
|
5
5
|
|
6
|
+
BEC Widgets can be used to compose a complete Qt graphical application, along with
|
7
|
+
other QWidgets. The only requirement is to connect to BEC servers in order to get
|
8
|
+
data, or to interact with BEC components. This role is devoted to the BECDispatcher,
|
9
|
+
a singleton object which has to be instantiated **after the QApplication is created**.
|
10
|
+
|
11
|
+
A typical BEC Widgets custom application "main" entry point should follow the template
|
12
|
+
below:
|
13
|
+
|
14
|
+
```
|
15
|
+
import argparse
|
16
|
+
import sys
|
17
|
+
|
18
|
+
from bec_widgets.utils.bec_dispatcher import BECDispatcher
|
19
|
+
from qtpy.QtWidgets import QApplication
|
20
|
+
|
21
|
+
# optional command line arguments processing
|
22
|
+
parser = argparse.ArgumentParser(description="...")
|
23
|
+
parser.add_argument( ...)
|
24
|
+
...
|
25
|
+
args = parser.parse_args()
|
26
|
+
|
27
|
+
# creation of the Qt application
|
28
|
+
app = QApplication([])
|
29
|
+
|
30
|
+
# creation of BEC Dispatcher
|
31
|
+
# /!\ important: after the QApplication has been instantiated
|
32
|
+
bec_dispatcher = BECDispatcher()
|
33
|
+
client = bec_dispatcher.client
|
34
|
+
client.start()
|
35
|
+
|
36
|
+
# (optional) processing of command line args,
|
37
|
+
# creation of a main window depending on the command line arguments (or not)
|
38
|
+
if args.xxx == "...":
|
39
|
+
window = ...
|
40
|
+
|
41
|
+
# display of the main window and start of Qt event loop
|
42
|
+
window.show()
|
43
|
+
sys.exit(app.exec())
|
44
|
+
```
|
45
|
+
|
46
|
+
The main "window" object presents the layout of widgets to the user and allows
|
47
|
+
users to interact. BEC Widgets must be placed in the window:
|
48
|
+
|
49
|
+
```
|
50
|
+
from qtpy.QWidgets import QMainWindow
|
51
|
+
from bec_widgets.widgets import BECFigure
|
52
|
+
|
53
|
+
window = QMainWindow()
|
54
|
+
bec_figure = BECFigure(gui_id="my_gui_app_id")
|
55
|
+
window.setCentralWidget(bec_figure)
|
56
|
+
|
57
|
+
# prepare to plot samx motor vs bpm4i value
|
58
|
+
bec_figure.plot(x_name="samx", y_name="bpm4i")
|
59
|
+
```
|
60
|
+
|
61
|
+
In the example just above, the resulting application will show a plot of samx
|
62
|
+
positions on the horizontal axis, and beam intensity on the vertical axis
|
63
|
+
(when the next scan will be started).
|
64
|
+
|
65
|
+
It is important to ensure proper cleanup of the resources is done when application
|
66
|
+
quits:
|
67
|
+
|
68
|
+
```
|
69
|
+
def final_cleanup():
|
70
|
+
bec_figure.clear_all()
|
71
|
+
bec_figure.client.shutdown()
|
72
|
+
|
73
|
+
window.aboutToQuit.connect(final_cleanup)
|
74
|
+
```
|
75
|
+
|
76
|
+
Final example:
|
77
|
+
|
78
|
+
```
|
79
|
+
import sys
|
80
|
+
from qtpy.QtWidgets import QMainWindow, QApplication
|
81
|
+
from bec_widgets.widgets import BECFigure
|
82
|
+
from bec_widgets.utils.bec_dispatcher import BECDispatcher
|
83
|
+
|
84
|
+
# creation of the Qt application
|
85
|
+
app = QApplication([])
|
86
|
+
|
87
|
+
# creation of BEC Dispatcher
|
88
|
+
bec_dispatcher = BECDispatcher()
|
89
|
+
client = bec_dispatcher.client
|
90
|
+
client.start()
|
91
|
+
|
92
|
+
# creation of main window
|
93
|
+
window = QMainWindow()
|
94
|
+
|
95
|
+
# inserting BEC Widgets
|
96
|
+
bec_figure = BECFigure(parent=window, gui_id="my_gui_app_id")
|
97
|
+
window.setCentralWidget(bec_figure)
|
98
|
+
|
99
|
+
bec_figure.plot(x_name="samx", y_name="bpm4i")
|
100
|
+
|
101
|
+
# ensuring proper cleanup
|
102
|
+
def final_cleanup():
|
103
|
+
bec_figure.clear_all()
|
104
|
+
bec_figure.client.shutdown()
|
105
|
+
|
106
|
+
app.aboutToQuit.connect(final_cleanup)
|
107
|
+
|
108
|
+
# execution
|
109
|
+
window.show()
|
110
|
+
sys.exit(app.exec())
|
111
|
+
```
|
112
|
+
|
113
|
+
## Writing applications using Qt Designer
|
114
|
+
|
115
|
+
BEC Widgets are designed to be used with QtDesigner to quickly design GUI.
|
6
116
|
|
7
117
|
## Example of promoting widgets in Qt Designer
|
8
118
|
|
@@ -0,0 +1,38 @@
|
|
1
|
+
(user.widgets.buttons)=
|
2
|
+
|
3
|
+
# Buttons Widgets
|
4
|
+
|
5
|
+
This section consolidates various custom buttons used within the BEC GUIs, facilitating the integration of these
|
6
|
+
controls into different layouts.
|
7
|
+
|
8
|
+
## Stop Button
|
9
|
+
|
10
|
+
**Purpose:**
|
11
|
+
|
12
|
+
The `Stop Button` provides a user interface control to immediately halt the execution of the current operation in the
|
13
|
+
BEC Client. It is designed for easy integration into any BEC GUI layout.
|
14
|
+
|
15
|
+
**Key Features:**
|
16
|
+
|
17
|
+
- **Immediate Termination:** Halts the execution of the current script or process.
|
18
|
+
- **Queue Management:** Clears any pending operations in the scan queue, ensuring the system is ready for new tasks.
|
19
|
+
|
20
|
+
**Code example:**
|
21
|
+
|
22
|
+
Integrating the `StopButton` into a BEC GUI layout is straightforward. The following example demonstrates how to embed
|
23
|
+
a `StopButton` within a GUI layout:
|
24
|
+
|
25
|
+
```python
|
26
|
+
from qtpy.QtWidgets import QWidget, QVBoxLayout
|
27
|
+
from bec_widgets.widgets import StopButton
|
28
|
+
|
29
|
+
|
30
|
+
class MyGui(QWidget):
|
31
|
+
def __init__(self):
|
32
|
+
super().__init__()
|
33
|
+
self.setLayout(QVBoxLayout(self)) # Initialize the layout for the widget
|
34
|
+
|
35
|
+
# Create and add the StopButton to the layout
|
36
|
+
self.stop_button = StopButton()
|
37
|
+
self.layout().addWidget(self.stop_button)
|
38
|
+
```
|
docs/user/widgets/widgets.md
CHANGED
pyproject.toml
CHANGED
@@ -253,8 +253,14 @@ def test_auto_update(bec_client_lib, rpc_server_dock):
|
|
253
253
|
plt_data = widgets[0].get_all_data()
|
254
254
|
|
255
255
|
# check plotted data
|
256
|
-
assert
|
257
|
-
|
256
|
+
assert (
|
257
|
+
plt_data[f"Scan {status.scan.scan_number} - bpm4i"]["x"]
|
258
|
+
== last_scan_data["samx"]["samx"].val
|
259
|
+
)
|
260
|
+
assert (
|
261
|
+
plt_data[f"Scan {status.scan.scan_number} - bpm4i"]["y"]
|
262
|
+
== last_scan_data["bpm4i"]["bpm4i"].val
|
263
|
+
)
|
258
264
|
|
259
265
|
status = scans.grid_scan(
|
260
266
|
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):
|
|
268
274
|
last_scan_data = queue.scan_storage.storage[-1].data
|
269
275
|
|
270
276
|
# check plotted data
|
271
|
-
assert
|
272
|
-
|
277
|
+
assert (
|
278
|
+
plt_data[f"Scan {status.scan.scan_number} - {dock.selected_device}"]["x"]
|
279
|
+
== last_scan_data["samx"]["samx"].val
|
280
|
+
)
|
281
|
+
assert (
|
282
|
+
plt_data[f"Scan {status.scan.scan_number} - {dock.selected_device}"]["y"]
|
283
|
+
== last_scan_data["samy"]["samy"].val
|
284
|
+
)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# pylint: disable=missing-function-docstring, missing-module-docstring, unused-import
|
2
|
+
|
3
|
+
import pytest
|
4
|
+
|
5
|
+
from bec_widgets.widgets import StopButton
|
6
|
+
|
7
|
+
from .client_mocks import mocked_client
|
8
|
+
|
9
|
+
|
10
|
+
@pytest.fixture
|
11
|
+
def stop_button(qtbot, mocked_client):
|
12
|
+
widget = StopButton(client=mocked_client)
|
13
|
+
qtbot.addWidget(widget)
|
14
|
+
qtbot.waitExposed(widget)
|
15
|
+
yield widget
|
16
|
+
widget.close()
|
17
|
+
|
18
|
+
|
19
|
+
def test_stop_button(stop_button):
|
20
|
+
assert stop_button.text() == "Stop"
|
21
|
+
assert stop_button.styleSheet() == "background-color: #cc181e; color: white"
|
22
|
+
stop_button.click()
|
23
|
+
assert stop_button.queue.request_scan_abortion.called
|
24
|
+
assert stop_button.queue.request_queue_reset.called
|
25
|
+
stop_button.close()
|
File without changes
|
File without changes
|
File without changes
|