bec-widgets 0.103.0__py3-none-any.whl → 0.105.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.
Files changed (33) hide show
  1. CHANGELOG.md +42 -42
  2. PKG-INFO +1 -1
  3. bec_widgets/cli/client.py +30 -0
  4. bec_widgets/cli/client_utils.py +5 -4
  5. bec_widgets/cli/server.py +21 -9
  6. bec_widgets/examples/plugin_example_pyside/tictactoe.py +0 -1
  7. bec_widgets/utils/bec_connector.py +5 -4
  8. bec_widgets/utils/bec_dispatcher.py +9 -13
  9. bec_widgets/utils/bec_widget.py +5 -1
  10. bec_widgets/widgets/dap_combo_box/__init__.py +0 -0
  11. bec_widgets/widgets/dap_combo_box/dap_combo_box.py +185 -0
  12. bec_widgets/widgets/dap_combo_box/dap_combo_box.pyproject +1 -0
  13. bec_widgets/widgets/dap_combo_box/dap_combo_box_plugin.py +54 -0
  14. bec_widgets/widgets/dap_combo_box/register_dap_combo_box.py +15 -0
  15. bec_widgets/widgets/device_browser/device_item/device_item.py +4 -1
  16. bec_widgets/widgets/figure/figure.py +4 -1
  17. bec_widgets/widgets/figure/plots/image/image.py +4 -1
  18. bec_widgets/widgets/figure/plots/image/image_item.py +4 -1
  19. bec_widgets/widgets/figure/plots/motor_map/motor_map.py +5 -2
  20. bec_widgets/widgets/figure/plots/plot_base.py +4 -1
  21. bec_widgets/widgets/figure/plots/waveform/waveform.py +7 -4
  22. bec_widgets/widgets/figure/plots/waveform/waveform_curve.py +4 -2
  23. bec_widgets/widgets/ring_progress_bar/ring_progress_bar.py +8 -5
  24. bec_widgets/widgets/scan_control/scan_control.py +183 -21
  25. bec_widgets/widgets/scan_control/scan_group_box.py +40 -13
  26. bec_widgets/widgets/toggle/toggle.py +3 -3
  27. bec_widgets/widgets/waveform/waveform_widget.py +4 -2
  28. {bec_widgets-0.103.0.dist-info → bec_widgets-0.105.0.dist-info}/METADATA +1 -1
  29. {bec_widgets-0.103.0.dist-info → bec_widgets-0.105.0.dist-info}/RECORD +33 -28
  30. pyproject.toml +1 -1
  31. {bec_widgets-0.103.0.dist-info → bec_widgets-0.105.0.dist-info}/WHEEL +0 -0
  32. {bec_widgets-0.103.0.dist-info → bec_widgets-0.105.0.dist-info}/entry_points.txt +0 -0
  33. {bec_widgets-0.103.0.dist-info → bec_widgets-0.105.0.dist-info}/licenses/LICENSE +0 -0
CHANGELOG.md CHANGED
@@ -1,5 +1,47 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## v0.105.0 (2024-09-04)
4
+
5
+ ### Feature
6
+
7
+ * feat: add dap_combobox ([`cc691d4`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/cc691d4039bde710e78f362d2f0e712f9e8f196f))
8
+
9
+ ### Refactor
10
+
11
+ * refactor: cleanup and renaming of slot/signals ([`0fd5cee`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/0fd5cee77611b6645326eaefa68455ea8de26597))
12
+
13
+ * refactor(logger): changed prints to logger calls ([`3a5d7d0`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3a5d7d07966ab9b38ba33bda0bed38c30f500c66))
14
+
15
+ ## v0.104.0 (2024-09-04)
16
+
17
+ ### Documentation
18
+
19
+ * docs(scan_control): docs extended ([`730e25f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/730e25fd3a8be156603005982bfd2a2c2b16dff1))
20
+
21
+ ### Feature
22
+
23
+ * feat(scan_control): scan control remember the previously set parameters and shares kwarg settings across scans ([`d28f9b0`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/d28f9b04c45385179353cc247221ec821dcaa29b))
24
+
25
+ ### Fix
26
+
27
+ * fix(scan_control): SafeSlot applied to run_scan to avoid faulty scan requests ([`9047916`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/90479167fb5cae393c884e71a80fcfdb48a76427))
28
+
29
+ * fix(scan_control): scan parameters can be loaded from the last executed scan from redis ([`ec3bc8b`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/ec3bc8b5194c680b847d3306c41eef4638ccfcc7))
30
+
31
+ * fix(toggle): state can be determined with the widget initialisation ([`2cd9c7f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/2cd9c7f5854f158468e53b5b29ec31b1ff1e00e6))
32
+
33
+ ### Refactor
34
+
35
+ * refactor(scan_control): scan control layout adjusted ([`85dcbda`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/85dcbdaa88fe77aeea7012bfc16f10c4f873f75e))
36
+
37
+ * refactor(scan_control): basic pydantic config added ([`fe8dc55`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/fe8dc55eb102c51c34bf9606690914da53b5ac02))
38
+
39
+ ### Test
40
+
41
+ * test(scan_control): tests extended for getting kwargs between scan switching and getting parameters from redis ([`b07e677`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b07e67715c9284e9bf36056ba4ba8068f60cbaf3))
42
+
43
+ * test(conftest): only run cleanup checks if test passed ([`26920f8`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/26920f8482bdb35ece46df37232af50ab9cab463))
44
+
3
45
  ## v0.103.0 (2024-09-04)
4
46
 
5
47
  ### Ci
@@ -113,45 +155,3 @@
113
155
  ### Refactor
114
156
 
115
157
  * refactor(icons): general app icon changed; jupyter app icon changed to material icon ([`5d73fe4`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/5d73fe455a568ad40a9fadc5ce6e249d782ad20d))
116
-
117
- * refactor: add option to select scan and hide arg bundle buttons ([`7dadab1`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/7dadab1f14aa41876ad39e8cdc7f7732248cc643))
118
-
119
- ## v0.99.10 (2024-08-29)
120
-
121
- ### Fix
122
-
123
- * fix(stop_button): queue logic scan changed to halt instead of abort and reset ([`4a89028`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4a890281f7eaef02d0ec9f4c5bf080be11fe0fe3))
124
-
125
- ### Refactor
126
-
127
- * refactor(stop_button): stop button changed to QWidget and adapted for toolbar ([`097946f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/097946fd688b8faf770e7cc0e689ea668206bc7a))
128
-
129
- * refactor: added hide option for device selection button ([`cdd1752`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/cdd175207e922904b2efbb2d9ecf7c556c617f2e))
130
-
131
- ## v0.99.9 (2024-08-28)
132
-
133
- ### Fix
134
-
135
- * fix: fixed build process and excluded docs and tests from tarballs and wheels ([`719254c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/719254cf0a48e1fc4bd541edba239570778bcfea))
136
-
137
- ## v0.99.8 (2024-08-28)
138
-
139
- ### Fix
140
-
141
- * fix(website): fixed designer integration for website widget ([`5f37e86`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/5f37e862c95ac7173b6918ad39bcaef938dad698))
142
-
143
- ### Refactor
144
-
145
- * refactor(website): changed inheritance of website widget to simple qwidget; closes #325 ([`9925bbd`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/9925bbdb48b55eacbbce9fd6a1555a21b84221f9))
146
-
147
- ## v0.99.7 (2024-08-28)
148
-
149
- ### Fix
150
-
151
- * fix(toolbar): material icons can accept color as kwarg ([`ffc871e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/ffc871ebbd3b68abc3e151bb8f5849e6c50e775e))
152
-
153
- ## v0.99.6 (2024-08-28)
154
-
155
- ### Fix
156
-
157
- * fix(toolbar): use of native qt separators ([`09c6c93`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/09c6c93c397ce4a21c293f6c79106c74b2db65ca))
PKG-INFO CHANGED
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bec_widgets
3
- Version: 0.103.0
3
+ Version: 0.105.0
4
4
  Summary: BEC Widgets
5
5
  Project-URL: Bug Tracker, https://gitlab.psi.ch/bec/bec_widgets/issues
6
6
  Project-URL: Homepage, https://gitlab.psi.ch/bec/bec_widgets
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"
@@ -2312,6 +2313,35 @@ class BECWaveformWidget(RPCBase):
2312
2313
  """
2313
2314
 
2314
2315
 
2316
+ class DapComboBox(RPCBase):
2317
+ @rpc_call
2318
+ def select_y_axis(self, y_axis: str):
2319
+ """
2320
+ Receive update signal for the y axis.
2321
+
2322
+ Args:
2323
+ y_axis(str): Y axis.
2324
+ """
2325
+
2326
+ @rpc_call
2327
+ def select_x_axis(self, x_axis: str):
2328
+ """
2329
+ Receive update signal for the x axis.
2330
+
2331
+ Args:
2332
+ x_axis(str): X axis.
2333
+ """
2334
+
2335
+ @rpc_call
2336
+ def select_fit(self, fit_name: str | None):
2337
+ """
2338
+ Select current fit.
2339
+
2340
+ Args:
2341
+ default_device(str): Default device name.
2342
+ """
2343
+
2344
+
2315
2345
  class DarkModeButton(RPCBase):
2316
2346
  @rpc_call
2317
2347
  def toggle_dark_mode(self) -> "None":
@@ -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
- print(f"Error reading process output: {str(e)}")
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
- print(f"Error loading auto update script from plugin: {str(e)}")
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
- print(e)
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
- self._log_func(lines)
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
- service_config.redis,
160
- QtRedisConnector,
161
- service_name="BECWidgetsCLIServer",
162
- service_config=service_config.service_config,
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.debug)):
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")
@@ -16,7 +16,6 @@ class TicTacToe(QWidget): # pragma: no cover
16
16
  super().__init__(parent)
17
17
  self._state = DEFAULT_STATE
18
18
  self._turn_number = 0
19
- print("TicTac HERE !!!!!!")
20
19
 
21
20
  def minimumSizeHint(self):
22
21
  return QSize(200, 200)
@@ -6,7 +6,7 @@ import time
6
6
  import uuid
7
7
  from typing import Optional
8
8
 
9
- import yaml
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
- print("Disconnecting", repr(dispatcher))
85
+ logger.info("Disconnecting", repr(dispatcher))
85
86
  dispatcher.disconnect_all()
86
- print("Shutting down BEC Client", repr(client))
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
- print(
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
- self.client = BECClientWithoutLoggerInit(
98
- config=config, connector_cls=QtRedisConnector
99
- ) # , forced=True)
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
- print("Could not connect to Redis, skipping start of BECClient.")
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
@@ -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"""
@@ -41,7 +44,7 @@ class BECWidget(BECConnector):
41
44
  """
42
45
  if not isinstance(self, QWidget):
43
46
  raise RuntimeError(f"{repr(self)} is not a subclass of QWidget")
44
- super().__init__(client, config, gui_id)
47
+ super().__init__(client=client, config=config, gui_id=gui_id)
45
48
 
46
49
  # Set the theme to auto if it is not set yet
47
50
  app = QApplication.instance()
@@ -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()