bec-widgets 0.104.0__py3-none-any.whl → 0.106.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 +26 -24
- PKG-INFO +1 -1
- bec_widgets/cli/client.py +39 -0
- bec_widgets/cli/client_utils.py +5 -4
- bec_widgets/cli/server.py +21 -9
- bec_widgets/examples/plugin_example_pyside/tictactoe.py +0 -1
- bec_widgets/utils/bec_connector.py +5 -4
- bec_widgets/utils/bec_dispatcher.py +9 -13
- bec_widgets/utils/bec_widget.py +4 -0
- bec_widgets/widgets/dap_combo_box/__init__.py +0 -0
- bec_widgets/widgets/dap_combo_box/dap_combo_box.py +185 -0
- bec_widgets/widgets/dap_combo_box/dap_combo_box.pyproject +1 -0
- bec_widgets/widgets/dap_combo_box/dap_combo_box_plugin.py +54 -0
- bec_widgets/widgets/dap_combo_box/register_dap_combo_box.py +15 -0
- bec_widgets/widgets/device_browser/device_item/device_item.py +4 -1
- bec_widgets/widgets/figure/figure.py +4 -1
- bec_widgets/widgets/figure/plots/axis_settings.py +3 -0
- bec_widgets/widgets/figure/plots/axis_settings.ui +38 -17
- bec_widgets/widgets/figure/plots/image/image.py +4 -1
- bec_widgets/widgets/figure/plots/image/image_item.py +4 -1
- bec_widgets/widgets/figure/plots/motor_map/motor_map.py +5 -2
- bec_widgets/widgets/figure/plots/plot_base.py +18 -1
- bec_widgets/widgets/figure/plots/waveform/waveform.py +7 -4
- bec_widgets/widgets/figure/plots/waveform/waveform_curve.py +4 -2
- bec_widgets/widgets/ring_progress_bar/ring_progress_bar.py +8 -5
- bec_widgets/widgets/scan_control/scan_group_box.py +6 -1
- bec_widgets/widgets/waveform/waveform_popups/curve_dialog/curve_dialog.py +6 -14
- bec_widgets/widgets/waveform/waveform_widget.py +13 -5
- {bec_widgets-0.104.0.dist-info → bec_widgets-0.106.0.dist-info}/METADATA +1 -1
- {bec_widgets-0.104.0.dist-info → bec_widgets-0.106.0.dist-info}/RECORD +34 -29
- pyproject.toml +1 -1
- {bec_widgets-0.104.0.dist-info → bec_widgets-0.106.0.dist-info}/WHEEL +0 -0
- {bec_widgets-0.104.0.dist-info → bec_widgets-0.106.0.dist-info}/entry_points.txt +0 -0
- {bec_widgets-0.104.0.dist-info → bec_widgets-0.106.0.dist-info}/licenses/LICENSE +0 -0
CHANGELOG.md
CHANGED
@@ -1,5 +1,31 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## v0.106.0 (2024-09-05)
|
4
|
+
|
5
|
+
### Feature
|
6
|
+
|
7
|
+
* feat(plot_base): toggle to switch outer axes for plotting widgets ([`06d7741`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/06d7741622aea8556208cd17cae521c37333f8b6))
|
8
|
+
|
9
|
+
### Refactor
|
10
|
+
|
11
|
+
* refactor: use DAPComboBox in curve_dialog selection ([`998a745`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/998a7451335b1b35c3e18691d3bab8d882e2d30b))
|
12
|
+
|
13
|
+
### Test
|
14
|
+
|
15
|
+
* test: fix tests ([`6b15abc`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6b15abcc73170cb49292741a619a08ee615e6250))
|
16
|
+
|
17
|
+
## v0.105.0 (2024-09-04)
|
18
|
+
|
19
|
+
### Feature
|
20
|
+
|
21
|
+
* feat: add dap_combobox ([`cc691d4`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/cc691d4039bde710e78f362d2f0e712f9e8f196f))
|
22
|
+
|
23
|
+
### Refactor
|
24
|
+
|
25
|
+
* refactor: cleanup and renaming of slot/signals ([`0fd5cee`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/0fd5cee77611b6645326eaefa68455ea8de26597))
|
26
|
+
|
27
|
+
* refactor(logger): changed prints to logger calls ([`3a5d7d0`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3a5d7d07966ab9b38ba33bda0bed38c30f500c66))
|
28
|
+
|
3
29
|
## v0.104.0 (2024-09-04)
|
4
30
|
|
5
31
|
### Documentation
|
@@ -131,27 +157,3 @@
|
|
131
157
|
* fix(toolbar): widget action added ([`2efd487`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/2efd48736cbe04e84533f7933c552ea8274e2162))
|
132
158
|
|
133
159
|
* fix(reset_button): reset button added ([`6ed1efc`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6ed1efc6af193908f70aa37fb73157d2ca6a62f4))
|
134
|
-
|
135
|
-
* fix(abort_button): abort button added; some minor fixes ([`a568633`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a568633c3206a8c26069d140f2d9a548bf4124b0))
|
136
|
-
|
137
|
-
## v0.99.11 (2024-08-29)
|
138
|
-
|
139
|
-
### Fix
|
140
|
-
|
141
|
-
* fix(resume_button): resume button added ([`8be8295`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/8be8295b2b38f36da210ab36c5da6d0a00e330cc))
|
142
|
-
|
143
|
-
### Refactor
|
144
|
-
|
145
|
-
* refactor(icons): general app icon changed; jupyter app icon changed to material icon ([`5d73fe4`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/5d73fe455a568ad40a9fadc5ce6e249d782ad20d))
|
146
|
-
|
147
|
-
* refactor: add option to select scan and hide arg bundle buttons ([`7dadab1`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/7dadab1f14aa41876ad39e8cdc7f7732248cc643))
|
148
|
-
|
149
|
-
## v0.99.10 (2024-08-29)
|
150
|
-
|
151
|
-
### Fix
|
152
|
-
|
153
|
-
* fix(stop_button): queue logic scan changed to halt instead of abort and reset ([`4a89028`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4a890281f7eaef02d0ec9f4c5bf080be11fe0fe3))
|
154
|
-
|
155
|
-
### Refactor
|
156
|
-
|
157
|
-
* refactor(stop_button): stop button changed to QWidget and adapted for toolbar ([`097946f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/097946fd688b8faf770e7cc0e689ea668206bc7a))
|
PKG-INFO
CHANGED
bec_widgets/cli/client.py
CHANGED
@@ -22,6 +22,7 @@ class Widgets(str, enum.Enum):
|
|
22
22
|
BECQueue = "BECQueue"
|
23
23
|
BECStatusBox = "BECStatusBox"
|
24
24
|
BECWaveformWidget = "BECWaveformWidget"
|
25
|
+
DapComboBox = "DapComboBox"
|
25
26
|
DarkModeButton = "DarkModeButton"
|
26
27
|
DeviceBrowser = "DeviceBrowser"
|
27
28
|
DeviceComboBox = "DeviceComboBox"
|
@@ -1652,6 +1653,15 @@ class BECPlotBase(RPCBase):
|
|
1652
1653
|
y(bool): Show grid on the y-axis.
|
1653
1654
|
"""
|
1654
1655
|
|
1656
|
+
@rpc_call
|
1657
|
+
def set_outer_axes(self, show: "bool" = True):
|
1658
|
+
"""
|
1659
|
+
Set the outer axes of the plot widget.
|
1660
|
+
|
1661
|
+
Args:
|
1662
|
+
show(bool): Show the outer axes.
|
1663
|
+
"""
|
1664
|
+
|
1655
1665
|
@rpc_call
|
1656
1666
|
def lock_aspect_ratio(self, lock):
|
1657
1667
|
"""
|
@@ -2312,6 +2322,35 @@ class BECWaveformWidget(RPCBase):
|
|
2312
2322
|
"""
|
2313
2323
|
|
2314
2324
|
|
2325
|
+
class DapComboBox(RPCBase):
|
2326
|
+
@rpc_call
|
2327
|
+
def select_y_axis(self, y_axis: str):
|
2328
|
+
"""
|
2329
|
+
Slot to update the y axis.
|
2330
|
+
|
2331
|
+
Args:
|
2332
|
+
y_axis(str): Y axis.
|
2333
|
+
"""
|
2334
|
+
|
2335
|
+
@rpc_call
|
2336
|
+
def select_x_axis(self, x_axis: str):
|
2337
|
+
"""
|
2338
|
+
Slot to update the x axis.
|
2339
|
+
|
2340
|
+
Args:
|
2341
|
+
x_axis(str): X axis.
|
2342
|
+
"""
|
2343
|
+
|
2344
|
+
@rpc_call
|
2345
|
+
def select_fit_model(self, fit_name: str | None):
|
2346
|
+
"""
|
2347
|
+
Slot to update the fit model.
|
2348
|
+
|
2349
|
+
Args:
|
2350
|
+
default_device(str): Default device name.
|
2351
|
+
"""
|
2352
|
+
|
2353
|
+
|
2315
2354
|
class DarkModeButton(RPCBase):
|
2316
2355
|
@rpc_call
|
2317
2356
|
def toggle_dark_mode(self) -> "None":
|
bec_widgets/cli/client_utils.py
CHANGED
@@ -80,7 +80,7 @@ def _get_output(process, logger) -> None:
|
|
80
80
|
buf.clear()
|
81
81
|
buf.append(remaining)
|
82
82
|
except Exception as e:
|
83
|
-
|
83
|
+
logger.error(f"Error reading process output: {str(e)}")
|
84
84
|
|
85
85
|
|
86
86
|
def _start_plot_process(gui_id: str, gui_class: type, config: dict | str, logger=None) -> None:
|
@@ -146,7 +146,7 @@ class BECGuiClientMixin:
|
|
146
146
|
continue
|
147
147
|
return ep.load()(gui=self)
|
148
148
|
except Exception as e:
|
149
|
-
|
149
|
+
logger.error(f"Error loading auto update script from plugin: {str(e)}")
|
150
150
|
return None
|
151
151
|
|
152
152
|
@property
|
@@ -189,11 +189,12 @@ class BECGuiClientMixin:
|
|
189
189
|
if self._process is None or self._process.poll() is not None:
|
190
190
|
self._start_update_script()
|
191
191
|
self._process, self._process_output_processing_thread = _start_plot_process(
|
192
|
-
self._gui_id, self.__class__, self._client._service_config.config
|
192
|
+
self._gui_id, self.__class__, self._client._service_config.config, logger=logger
|
193
193
|
)
|
194
194
|
while not self.gui_is_alive():
|
195
195
|
print("Waiting for GUI to start...")
|
196
196
|
time.sleep(1)
|
197
|
+
logger.success(f"GUI started with id: {self._gui_id}")
|
197
198
|
|
198
199
|
def close(self) -> None:
|
199
200
|
"""
|
@@ -226,7 +227,7 @@ class RPCBase:
|
|
226
227
|
def __init__(self, gui_id: str = None, config: dict = None, parent=None) -> None:
|
227
228
|
self._client = BECClient() # BECClient is a singleton; here, we simply get the instance
|
228
229
|
self._config = config if config is not None else {}
|
229
|
-
self._gui_id = gui_id if gui_id is not None else str(uuid.uuid4())
|
230
|
+
self._gui_id = gui_id if gui_id is not None else str(uuid.uuid4())[:5]
|
230
231
|
self._parent = parent
|
231
232
|
self._msg_wait_event = threading.Event()
|
232
233
|
self._rpc_response = None
|
bec_widgets/cli/server.py
CHANGED
@@ -53,9 +53,11 @@ class BECWidgetsCLIServer:
|
|
53
53
|
self._heartbeat_timer.start(200)
|
54
54
|
|
55
55
|
self.status = messages.BECStatus.RUNNING
|
56
|
+
logger.success(f"Server started with gui_id: {self.gui_id}")
|
56
57
|
|
57
58
|
def on_rpc_update(self, msg: dict, metadata: dict):
|
58
59
|
request_id = metadata.get("request_id")
|
60
|
+
logger.debug(f"Received RPC instruction: {msg}, metadata: {metadata}")
|
59
61
|
try:
|
60
62
|
obj = self.get_object_from_config(msg["parameter"])
|
61
63
|
method = msg["action"]
|
@@ -63,9 +65,10 @@ class BECWidgetsCLIServer:
|
|
63
65
|
kwargs = msg["parameter"].get("kwargs", {})
|
64
66
|
res = self.run_rpc(obj, method, args, kwargs)
|
65
67
|
except Exception as e:
|
66
|
-
|
68
|
+
logger.error(f"Error while executing RPC instruction: {e}")
|
67
69
|
self.send_response(request_id, False, {"error": str(e)})
|
68
70
|
else:
|
71
|
+
logger.debug(f"RPC instruction executed successfully: {res}")
|
69
72
|
self.send_response(request_id, True, {"result": res})
|
70
73
|
|
71
74
|
def send_response(self, request_id: str, accepted: bool, msg: dict):
|
@@ -113,6 +116,7 @@ class BECWidgetsCLIServer:
|
|
113
116
|
return obj
|
114
117
|
|
115
118
|
def emit_heartbeat(self):
|
119
|
+
logger.trace(f"Emitting heartbeat for {self.gui_id}")
|
116
120
|
self.client.connector.set(
|
117
121
|
MessageEndpoints.gui_heartbeat(self.gui_id),
|
118
122
|
messages.StatusMessage(name=self.gui_id, status=self.status, info={}),
|
@@ -120,6 +124,7 @@ class BECWidgetsCLIServer:
|
|
120
124
|
)
|
121
125
|
|
122
126
|
def shutdown(self): # TODO not sure if needed when cleanup is done at level of BECConnector
|
127
|
+
logger.info(f"Shutting down server with gui_id: {self.gui_id}")
|
123
128
|
self.status = messages.BECStatus.IDLE
|
124
129
|
self._heartbeat_timer.stop()
|
125
130
|
self.emit_heartbeat()
|
@@ -137,7 +142,8 @@ class SimpleFileLikeFromLogOutputFunc:
|
|
137
142
|
|
138
143
|
def flush(self):
|
139
144
|
lines, _, remaining = "".join(self._buffer).rpartition("\n")
|
140
|
-
|
145
|
+
if lines:
|
146
|
+
self._log_func(lines)
|
141
147
|
self._buffer = [remaining]
|
142
148
|
|
143
149
|
def close(self):
|
@@ -155,12 +161,12 @@ def _start_server(gui_id: str, gui_class: Union[BECFigure, BECDockArea], config:
|
|
155
161
|
# if no config is provided, use the default config
|
156
162
|
service_config = ServiceConfig()
|
157
163
|
|
158
|
-
bec_logger.configure(
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
)
|
164
|
+
# bec_logger.configure(
|
165
|
+
# service_config.redis,
|
166
|
+
# QtRedisConnector,
|
167
|
+
# service_name="BECWidgetsCLIServer",
|
168
|
+
# service_config=service_config.service_config,
|
169
|
+
# )
|
164
170
|
server = BECWidgetsCLIServer(gui_id=gui_id, config=service_config, gui_class=gui_class)
|
165
171
|
return server
|
166
172
|
|
@@ -175,6 +181,12 @@ def main():
|
|
175
181
|
|
176
182
|
import bec_widgets
|
177
183
|
|
184
|
+
bec_logger.level = bec_logger.LOGLEVEL.DEBUG
|
185
|
+
if __name__ != "__main__":
|
186
|
+
# if not running as main, set the log level to critical
|
187
|
+
# pylint: disable=protected-access
|
188
|
+
bec_logger._stderr_log_level = bec_logger.LOGLEVEL.CRITICAL
|
189
|
+
|
178
190
|
parser = argparse.ArgumentParser(description="BEC Widgets CLI Server")
|
179
191
|
parser.add_argument("--id", type=str, help="The id of the server")
|
180
192
|
parser.add_argument(
|
@@ -197,7 +209,7 @@ def main():
|
|
197
209
|
)
|
198
210
|
gui_class = BECFigure
|
199
211
|
|
200
|
-
with redirect_stdout(SimpleFileLikeFromLogOutputFunc(logger.
|
212
|
+
with redirect_stdout(SimpleFileLikeFromLogOutputFunc(logger.info)):
|
201
213
|
with redirect_stderr(SimpleFileLikeFromLogOutputFunc(logger.error)):
|
202
214
|
app = QApplication(sys.argv)
|
203
215
|
app.setApplicationName("BEC Figure")
|
@@ -6,7 +6,7 @@ import time
|
|
6
6
|
import uuid
|
7
7
|
from typing import Optional
|
8
8
|
|
9
|
-
import
|
9
|
+
from bec_lib.logger import bec_logger
|
10
10
|
from bec_lib.utils.import_utils import lazy_import_from
|
11
11
|
from pydantic import BaseModel, Field, field_validator
|
12
12
|
from qtpy.QtCore import QObject, QRunnable, QThreadPool, Signal
|
@@ -17,6 +17,7 @@ from bec_widgets.qt_utils.error_popups import ErrorPopupUtility
|
|
17
17
|
from bec_widgets.qt_utils.error_popups import SafeSlot as pyqtSlot
|
18
18
|
from bec_widgets.utils.yaml_dialog import load_yaml, load_yaml_gui, save_yaml, save_yaml_gui
|
19
19
|
|
20
|
+
logger = bec_logger.logger
|
20
21
|
BECDispatcher = lazy_import_from("bec_widgets.utils.bec_dispatcher", ("BECDispatcher",))
|
21
22
|
|
22
23
|
|
@@ -81,9 +82,9 @@ class BECConnector:
|
|
81
82
|
# the function depends on BECClient, and BECDispatcher
|
82
83
|
@pyqtSlot()
|
83
84
|
def terminate(client=self.client, dispatcher=self.bec_dispatcher):
|
84
|
-
|
85
|
+
logger.info("Disconnecting", repr(dispatcher))
|
85
86
|
dispatcher.disconnect_all()
|
86
|
-
|
87
|
+
logger.info("Shutting down BEC Client", repr(client))
|
87
88
|
client.shutdown()
|
88
89
|
|
89
90
|
BECConnector.EXIT_HANDLERS[self.client] = terminate
|
@@ -93,7 +94,7 @@ class BECConnector:
|
|
93
94
|
self.config = config
|
94
95
|
self.config.widget_class = self.__class__.__name__
|
95
96
|
else:
|
96
|
-
|
97
|
+
logger.debug(
|
97
98
|
f"No initial config found for {self.__class__.__name__}.\n"
|
98
99
|
f"Initializing with default config."
|
99
100
|
)
|
@@ -6,11 +6,14 @@ from typing import TYPE_CHECKING, Union
|
|
6
6
|
|
7
7
|
import redis
|
8
8
|
from bec_lib.client import BECClient
|
9
|
+
from bec_lib.logger import bec_logger
|
9
10
|
from bec_lib.redis_connector import MessageObject, RedisConnector
|
10
11
|
from bec_lib.service_config import ServiceConfig
|
11
12
|
from qtpy.QtCore import QObject
|
12
13
|
from qtpy.QtCore import Signal as pyqtSignal
|
13
14
|
|
15
|
+
logger = bec_logger.logger
|
16
|
+
|
14
17
|
if TYPE_CHECKING:
|
15
18
|
from bec_lib.endpoints import EndpointInfo
|
16
19
|
|
@@ -65,11 +68,6 @@ class QtRedisConnector(RedisConnector):
|
|
65
68
|
cb(msg.content, msg.metadata)
|
66
69
|
|
67
70
|
|
68
|
-
class BECClientWithoutLoggerInit(BECClient):
|
69
|
-
def _initialize_logger(self):
|
70
|
-
return
|
71
|
-
|
72
|
-
|
73
71
|
class BECDispatcher:
|
74
72
|
"""Utility class to keep track of slots connected to a particular redis connector"""
|
75
73
|
|
@@ -94,24 +92,22 @@ class BECDispatcher:
|
|
94
92
|
if not isinstance(config, ServiceConfig):
|
95
93
|
# config is supposed to be a path
|
96
94
|
config = ServiceConfig(config)
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
else:
|
101
|
-
self.client = BECClientWithoutLoggerInit(
|
102
|
-
connector_cls=QtRedisConnector
|
103
|
-
) # , forced=True)
|
95
|
+
self.client = BECClient(
|
96
|
+
config=config, connector_cls=QtRedisConnector, name="BECWidgets"
|
97
|
+
)
|
104
98
|
else:
|
105
99
|
if self.client.started:
|
106
100
|
# have to reinitialize client to use proper connector
|
101
|
+
logger.info("Shutting down BECClient to switch to QtRedisConnector")
|
107
102
|
self.client.shutdown()
|
108
103
|
self.client._BECClient__init_params["connector_cls"] = QtRedisConnector
|
109
104
|
|
110
105
|
try:
|
111
106
|
self.client.start()
|
112
107
|
except redis.exceptions.ConnectionError:
|
113
|
-
|
108
|
+
logger.warning("Could not connect to Redis, skipping start of BECClient.")
|
114
109
|
|
110
|
+
logger.success("Initialized BECDispatcher")
|
115
111
|
self._initialized = True
|
116
112
|
|
117
113
|
@classmethod
|
bec_widgets/utils/bec_widget.py
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import darkdetect
|
4
|
+
from bec_lib.logger import bec_logger
|
4
5
|
from qtpy.QtCore import Slot
|
5
6
|
from qtpy.QtWidgets import QApplication, QWidget
|
6
7
|
|
7
8
|
from bec_widgets.utils.bec_connector import BECConnector, ConnectionConfig
|
8
9
|
from bec_widgets.utils.colors import set_theme
|
9
10
|
|
11
|
+
logger = bec_logger.logger
|
12
|
+
|
10
13
|
|
11
14
|
class BECWidget(BECConnector):
|
12
15
|
"""Mixin class for all BEC widgets, to handle cleanup"""
|
@@ -54,6 +57,7 @@ class BECWidget(BECConnector):
|
|
54
57
|
set_theme("light")
|
55
58
|
|
56
59
|
if theme_update:
|
60
|
+
logger.debug(f"Subscribing to theme updates for {self.__class__.__name__}")
|
57
61
|
self._connect_to_theme_change()
|
58
62
|
|
59
63
|
def _connect_to_theme_change(self):
|
File without changes
|
@@ -0,0 +1,185 @@
|
|
1
|
+
""" Module for DapComboBox widget class to select a DAP model from a combobox. """
|
2
|
+
|
3
|
+
from bec_lib.logger import bec_logger
|
4
|
+
from qtpy.QtCore import Property, Signal, Slot
|
5
|
+
from qtpy.QtWidgets import QComboBox, QVBoxLayout, QWidget
|
6
|
+
|
7
|
+
from bec_widgets.utils.bec_widget import BECWidget
|
8
|
+
|
9
|
+
logger = bec_logger.logger
|
10
|
+
|
11
|
+
|
12
|
+
class DapComboBox(BECWidget, QWidget):
|
13
|
+
"""
|
14
|
+
The DAPComboBox widget is an extension to the QComboBox with all avaialble DAP model from BEC.
|
15
|
+
|
16
|
+
Args:
|
17
|
+
parent: Parent widget.
|
18
|
+
client: BEC client object.
|
19
|
+
gui_id: GUI ID.
|
20
|
+
default: Default device name.
|
21
|
+
"""
|
22
|
+
|
23
|
+
ICON_NAME = "data_exploration"
|
24
|
+
|
25
|
+
USER_ACCESS = ["select_y_axis", "select_x_axis", "select_fit_model"]
|
26
|
+
|
27
|
+
### Signals ###
|
28
|
+
# Signal to emit a new dap_config: (x_axis, y_axis, fit_model). Can be used to add a new DAP process
|
29
|
+
# in the BECWaveformWidget using its add_dap method. The signal is emitted when the user selects a new
|
30
|
+
# fit model, but only if x_axis and y_axis are set.
|
31
|
+
new_dap_config = Signal(str, str, str)
|
32
|
+
# Signal to emit the name of the updated x_axis
|
33
|
+
x_axis_updated = Signal(str)
|
34
|
+
# Signal to emit the name of the updated y_axis
|
35
|
+
y_axis_updated = Signal(str)
|
36
|
+
# Signal to emit the name of the updated fit model
|
37
|
+
fit_model_updated = Signal(str)
|
38
|
+
|
39
|
+
def __init__(
|
40
|
+
self, parent=None, client=None, gui_id: str | None = None, default_fit: str | None = None
|
41
|
+
):
|
42
|
+
super().__init__(client=client, gui_id=gui_id)
|
43
|
+
QWidget.__init__(self, parent=parent)
|
44
|
+
self.layout = QVBoxLayout(self)
|
45
|
+
self.fit_model_combobox = QComboBox(self)
|
46
|
+
self.layout.addWidget(self.fit_model_combobox)
|
47
|
+
self.layout.setContentsMargins(0, 0, 0, 0)
|
48
|
+
self._available_models = None
|
49
|
+
self._x_axis = None
|
50
|
+
self._y_axis = None
|
51
|
+
self.populate_fit_model_combobox()
|
52
|
+
self.fit_model_combobox.currentTextChanged.connect(self._update_current_fit)
|
53
|
+
# Set default fit model
|
54
|
+
self.select_default_fit(default_fit)
|
55
|
+
|
56
|
+
def select_default_fit(self, default_fit: str | None):
|
57
|
+
"""Set the default fit model.
|
58
|
+
|
59
|
+
Args:
|
60
|
+
default_fit(str): Default fit model.
|
61
|
+
"""
|
62
|
+
if self._validate_dap_model(default_fit):
|
63
|
+
self.select_fit_model(default_fit)
|
64
|
+
else:
|
65
|
+
self.select_fit_model("GaussianModel")
|
66
|
+
|
67
|
+
@property
|
68
|
+
def available_models(self):
|
69
|
+
"""Available models property."""
|
70
|
+
return self._available_models
|
71
|
+
|
72
|
+
@available_models.setter
|
73
|
+
def available_models(self, available_models: list[str]):
|
74
|
+
"""Set the available models.
|
75
|
+
|
76
|
+
Args:
|
77
|
+
available_models(list[str]): Available models.
|
78
|
+
"""
|
79
|
+
self._available_models = available_models
|
80
|
+
|
81
|
+
@Property(str)
|
82
|
+
def x_axis(self):
|
83
|
+
"""X axis property."""
|
84
|
+
return self._x_axis
|
85
|
+
|
86
|
+
@x_axis.setter
|
87
|
+
def x_axis(self, x_axis: str):
|
88
|
+
"""Set the x axis.
|
89
|
+
|
90
|
+
Args:
|
91
|
+
x_axis(str): X axis.
|
92
|
+
"""
|
93
|
+
# TODO add validator for x axis -> Positioner? or also device (must be monitored)!!
|
94
|
+
self._x_axis = x_axis
|
95
|
+
self.x_axis_updated.emit(x_axis)
|
96
|
+
|
97
|
+
@Property(str)
|
98
|
+
def y_axis(self):
|
99
|
+
"""Y axis property."""
|
100
|
+
# TODO add validator for y axis -> Positioner & Device? Must be a monitored device!!
|
101
|
+
return self._y_axis
|
102
|
+
|
103
|
+
@y_axis.setter
|
104
|
+
def y_axis(self, y_axis: str):
|
105
|
+
"""Set the y axis.
|
106
|
+
|
107
|
+
Args:
|
108
|
+
y_axis(str): Y axis.
|
109
|
+
"""
|
110
|
+
self._y_axis = y_axis
|
111
|
+
self.y_axis_updated.emit(y_axis)
|
112
|
+
|
113
|
+
def _update_current_fit(self, fit_name: str):
|
114
|
+
"""Update the current fit."""
|
115
|
+
self.fit_model_updated.emit(fit_name)
|
116
|
+
if self.x_axis is not None and self.y_axis is not None:
|
117
|
+
self.new_dap_config.emit(self._x_axis, self._y_axis, fit_name)
|
118
|
+
|
119
|
+
@Slot(str)
|
120
|
+
def select_x_axis(self, x_axis: str):
|
121
|
+
"""Slot to update the x axis.
|
122
|
+
|
123
|
+
Args:
|
124
|
+
x_axis(str): X axis.
|
125
|
+
"""
|
126
|
+
self.x_axis = x_axis
|
127
|
+
|
128
|
+
@Slot(str)
|
129
|
+
def select_y_axis(self, y_axis: str):
|
130
|
+
"""Slot to update the y axis.
|
131
|
+
|
132
|
+
Args:
|
133
|
+
y_axis(str): Y axis.
|
134
|
+
"""
|
135
|
+
self.y_axis = y_axis
|
136
|
+
|
137
|
+
@Slot(str)
|
138
|
+
def select_fit_model(self, fit_name: str | None):
|
139
|
+
"""Slot to update the fit model.
|
140
|
+
|
141
|
+
Args:
|
142
|
+
default_device(str): Default device name.
|
143
|
+
"""
|
144
|
+
if not self._validate_dap_model(fit_name):
|
145
|
+
raise ValueError(f"Fit {fit_name} is not valid.")
|
146
|
+
self.fit_model_combobox.setCurrentText(fit_name)
|
147
|
+
|
148
|
+
def populate_fit_model_combobox(self):
|
149
|
+
"""Populate the fit_model_combobox with the devices."""
|
150
|
+
# pylint: disable=protected-access
|
151
|
+
self.available_models = [model for model in self.client.dap._available_dap_plugins.keys()]
|
152
|
+
self.fit_model_combobox.clear()
|
153
|
+
self.fit_model_combobox.addItems(self.available_models)
|
154
|
+
|
155
|
+
def _validate_dap_model(self, model: str | None) -> bool:
|
156
|
+
"""Validate the DAP model.
|
157
|
+
|
158
|
+
Args:
|
159
|
+
model(str): Model name.
|
160
|
+
"""
|
161
|
+
if model is None:
|
162
|
+
return False
|
163
|
+
if model not in self.available_models:
|
164
|
+
return False
|
165
|
+
return True
|
166
|
+
|
167
|
+
|
168
|
+
# pragma: no cover
|
169
|
+
def main():
|
170
|
+
"""Main function to run the DapComboBox widget."""
|
171
|
+
import sys
|
172
|
+
|
173
|
+
from qtpy.QtWidgets import QApplication
|
174
|
+
|
175
|
+
from bec_widgets.utils.colors import set_theme
|
176
|
+
|
177
|
+
app = QApplication(sys.argv)
|
178
|
+
set_theme("auto")
|
179
|
+
widget = DapComboBox()
|
180
|
+
widget.show()
|
181
|
+
sys.exit(app.exec_())
|
182
|
+
|
183
|
+
|
184
|
+
if __name__ == "__main__":
|
185
|
+
main()
|
@@ -0,0 +1 @@
|
|
1
|
+
{'files': ['dap_combo_box.py']}
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# Copyright (C) 2022 The Qt Company Ltd.
|
2
|
+
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
3
|
+
|
4
|
+
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
5
|
+
|
6
|
+
from bec_widgets.utils.bec_designer import designer_material_icon
|
7
|
+
from bec_widgets.widgets.dap_combo_box.dap_combo_box import DapComboBox
|
8
|
+
|
9
|
+
DOM_XML = """
|
10
|
+
<ui language='c++'>
|
11
|
+
<widget class='DapComboBox' name='dap_combo_box'>
|
12
|
+
</widget>
|
13
|
+
</ui>
|
14
|
+
"""
|
15
|
+
|
16
|
+
|
17
|
+
class DapComboBoxPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
|
18
|
+
def __init__(self):
|
19
|
+
super().__init__()
|
20
|
+
self._form_editor = None
|
21
|
+
|
22
|
+
def createWidget(self, parent):
|
23
|
+
t = DapComboBox(parent)
|
24
|
+
return t
|
25
|
+
|
26
|
+
def domXml(self):
|
27
|
+
return DOM_XML
|
28
|
+
|
29
|
+
def group(self):
|
30
|
+
return "BEC Selection Widgets"
|
31
|
+
|
32
|
+
def icon(self):
|
33
|
+
return designer_material_icon(DapComboBox.ICON_NAME)
|
34
|
+
|
35
|
+
def includeFile(self):
|
36
|
+
return "dap_combo_box"
|
37
|
+
|
38
|
+
def initialize(self, form_editor):
|
39
|
+
self._form_editor = form_editor
|
40
|
+
|
41
|
+
def isContainer(self):
|
42
|
+
return False
|
43
|
+
|
44
|
+
def isInitialized(self):
|
45
|
+
return self._form_editor is not None
|
46
|
+
|
47
|
+
def name(self):
|
48
|
+
return "DapComboBox"
|
49
|
+
|
50
|
+
def toolTip(self):
|
51
|
+
return ""
|
52
|
+
|
53
|
+
def whatsThis(self):
|
54
|
+
return self.toolTip()
|
@@ -0,0 +1,15 @@
|
|
1
|
+
def main(): # pragma: no cover
|
2
|
+
from qtpy import PYSIDE6
|
3
|
+
|
4
|
+
if not PYSIDE6:
|
5
|
+
print("PYSIDE6 is not available in the environment. Cannot patch designer.")
|
6
|
+
return
|
7
|
+
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
8
|
+
|
9
|
+
from bec_widgets.widgets.dap_combo_box.dap_combo_box_plugin import DapComboBoxPlugin
|
10
|
+
|
11
|
+
QPyDesignerCustomWidgetCollection.addCustomWidget(DapComboBoxPlugin())
|
12
|
+
|
13
|
+
|
14
|
+
if __name__ == "__main__": # pragma: no cover
|
15
|
+
main()
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
from typing import TYPE_CHECKING
|
4
4
|
|
5
|
+
from bec_lib.logger import bec_logger
|
5
6
|
from qtpy.QtCore import QMimeData, Qt
|
6
7
|
from qtpy.QtGui import QDrag
|
7
8
|
from qtpy.QtWidgets import QHBoxLayout, QLabel, QWidget
|
@@ -9,6 +10,8 @@ from qtpy.QtWidgets import QHBoxLayout, QLabel, QWidget
|
|
9
10
|
if TYPE_CHECKING:
|
10
11
|
from qtpy.QtGui import QMouseEvent
|
11
12
|
|
13
|
+
logger = bec_logger.logger
|
14
|
+
|
12
15
|
|
13
16
|
class DeviceItem(QWidget):
|
14
17
|
def __init__(self, device: str) -> None:
|
@@ -37,7 +40,7 @@ class DeviceItem(QWidget):
|
|
37
40
|
drag.exec_(Qt.MoveAction)
|
38
41
|
|
39
42
|
def mouseDoubleClickEvent(self, event: QMouseEvent) -> None:
|
40
|
-
|
43
|
+
logger.debug("Double Clicked")
|
41
44
|
# TODO: Implement double click action for opening the device properties dialog
|
42
45
|
return super().mouseDoubleClickEvent(event)
|
43
46
|
|