bec-widgets 0.83.0__py3-none-any.whl → 0.84.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 (30) hide show
  1. CHANGELOG.md +50 -54
  2. PKG-INFO +2 -2
  3. bec_widgets/cli/client.py +61 -8
  4. bec_widgets/examples/jupyter_console/jupyter_console_window.py +107 -59
  5. bec_widgets/qt_utils/toolbar.py +3 -1
  6. bec_widgets/utils/bec_dispatcher.py +5 -2
  7. bec_widgets/utils/colors.py +27 -0
  8. bec_widgets/widgets/bec_status_box/bec_status_box.py +2 -2
  9. bec_widgets/widgets/device_box/device_box.py +2 -2
  10. bec_widgets/widgets/figure/figure.py +23 -117
  11. bec_widgets/widgets/figure/plots/axis_settings.py +2 -2
  12. bec_widgets/widgets/figure/plots/waveform/waveform.py +651 -94
  13. bec_widgets/widgets/figure/plots/waveform/waveform_curve.py +9 -2
  14. bec_widgets/widgets/scan_control/scan_control.py +2 -2
  15. bec_widgets/widgets/spinner/spinner.py +4 -3
  16. {bec_widgets-0.83.0.dist-info → bec_widgets-0.84.0.dist-info}/METADATA +2 -2
  17. {bec_widgets-0.83.0.dist-info → bec_widgets-0.84.0.dist-info}/RECORD +29 -30
  18. pyproject.toml +2 -2
  19. tests/references/SpinnerWidget/SpinnerWidget_darwin.png +0 -0
  20. tests/references/SpinnerWidget/SpinnerWidget_linux.png +0 -0
  21. tests/references/SpinnerWidget/SpinnerWidget_started_darwin.png +0 -0
  22. tests/references/SpinnerWidget/SpinnerWidget_started_linux.png +0 -0
  23. tests/unit_tests/client_mocks.py +13 -4
  24. tests/unit_tests/test_device_input_widgets.py +2 -0
  25. tests/unit_tests/test_spinner.py +2 -2
  26. tests/unit_tests/test_waveform1d.py +202 -23
  27. bec_widgets/examples/jupyter_console/jupyter_console_window.ui +0 -54
  28. {bec_widgets-0.83.0.dist-info → bec_widgets-0.84.0.dist-info}/WHEEL +0 -0
  29. {bec_widgets-0.83.0.dist-info → bec_widgets-0.84.0.dist-info}/entry_points.txt +0 -0
  30. {bec_widgets-0.83.0.dist-info → bec_widgets-0.84.0.dist-info}/licenses/LICENSE +0 -0
CHANGELOG.md CHANGED
@@ -1,5 +1,55 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## v0.84.0 (2024-07-15)
4
+
5
+ ### Feature
6
+
7
+ * feat(waveform): async readback update implemented for async devices ([`0c6a9f2`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/0c6a9f2310df31ddcd68050a17cfbf52c3e2e226))
8
+
9
+ * feat(waveform): data are taken directly from ScanItem which is defined from scan_status endpoint; scan update is triggered from scan_segment; plots can be added just specifying y_name -> best effort for setting x reported device ([`b8717f1`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b8717f13276734dd655ab03cd6005985ad5af9fb))
10
+
11
+ ### Fix
12
+
13
+ * fix(waveform): timestamp are not converted to human readable format ([`e495fd3`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/e495fd30c4c16474689943c7263e3060cb09ffb4))
14
+
15
+ * fix(waveform): set_x method various bugs fixed ([`8516a1d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/8516a1d639925a877f174fa13f427a71131cc918))
16
+
17
+ * fix(waveform): x axis switching logic fixed when axis are not compatible ([`e4e1a90`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/e4e1a905d19def22f970b364c18c953f00e10389))
18
+
19
+ * fix(waveform): dap leaked RID for all daps in current process; dap RID is now f"{scan_id}-{gui_id}" to distinguish for each plot instance ([`d23fd8b`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/d23fd8bd074ede6e14eb8e85e025cbced4bd45ef))
20
+
21
+ * fix(waveform): only one type of x axis allowed; x mode validated ([`9d6ae87`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/9d6ae87d0f03ca227570fcca8af2d8190828d271))
22
+
23
+ * fix(waveform): data for axis are taken by separate method; validation consolidated ([`fc5a8bd`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/fc5a8bdd8b260f5e9b59ec71a4610c57442e43fe))
24
+
25
+ * fix(bec_dispatcher): connect_slot can accept kwargs ([`0aa317a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/0aa317aae58d3612d46f05b85f8b0db3d12bbe14))
26
+
27
+ ### Refactor
28
+
29
+ * refactor(waveform): plot can be prompted without specifying kwargs ([`48911e9`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/48911e934815923c94edb5ced6042058a11a97f5))
30
+
31
+ * refactor(jupyter_console_window): added more examples of waveforms ([`fc935d9`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/fc935d9fc81067c3a67389ff88ea97da2e0c903e))
32
+
33
+ ### Test
34
+
35
+ * test(waveform): tests extended ([`006992e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/006992e43cc56d56261bc4fd3e9cae9abcab2153))
36
+
37
+ ## v0.83.1 (2024-07-14)
38
+
39
+ ### Fix
40
+
41
+ * fix(toolbar): default transparent background ([`eab7883`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/eab78839792f175b7ac127ca603385c6baa5ff15))
42
+
43
+ * fix: use apply_theme ([`2d4249e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/2d4249e73a792fed1c2c7ab79bb8aec38c57466c))
44
+
45
+ * fix: spinner: update reference image for widget test, use apply_theme ([`63db135`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/63db1352ee883d35670b3a692dbe51d6d01872ae))
46
+
47
+ * fix: replace pyqtdarktheme by qdarkstyle, add 'apply_theme' function (in utils/colors.py) ([`8308115`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/8308115f3646245d825fc47ab57297d3460bbcf5))
48
+
49
+ ### Test
50
+
51
+ * test(toolbar): added reference pngs for spinner for Darwin ([`11a7204`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/11a7204c98e0bf211a8721d296b45d24a3102b97))
52
+
3
53
  ## v0.83.0 (2024-07-08)
4
54
 
5
55
  ### Feature
@@ -89,57 +139,3 @@
89
139
  * feat(color_button): can get colors in RGBA or HEX ([`9594be2`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/9594be260680d11c8550ff74ffb8d679e5a5b8f6))
90
140
 
91
141
  ## v0.80.1 (2024-07-06)
92
-
93
- ### Fix
94
-
95
- * fix(entry_validator): check for entry == "" ([`61de7e9`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/61de7e9e221c766b9fb3ec23246da6a11c96a986))
96
-
97
- ## v0.80.0 (2024-07-06)
98
-
99
- ### Feature
100
-
101
- * feat(qt5): dropped support for qt5; pyside2 and pyqt5 ([`fadbf77`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/fadbf77866903beff6580802bc203d53367fc7e7))
102
-
103
- * feat(plugins): moved plugin dict to dataclass and container ([`03819a3`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/03819a3d902b4a51f3e882d52aedd971b2a8e127))
104
-
105
- * feat(plugins): added support for pyqt6 ui files ([`d6d0777`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/d6d07771135335cb78dc648508ce573b8970261a))
106
-
107
- * feat(plugins): added bec widgets base class ([`1aa83e0`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1aa83e0ef1ffe45b01677b0b4590535cb0ca1cff))
108
-
109
- ## v0.79.3 (2024-07-05)
110
-
111
- ### Fix
112
-
113
- * fix: changed inheritance to adress qt designer bug in rendering ([`e403870`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/e403870874bd5e45840a034d6f1b3dd576d9c846))
114
-
115
- * fix: add designer plugin classes ([`1586ce2`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1586ce2d6cba2bb086b2ef596e724bb9e40ab4f2))
116
-
117
- ### Refactor
118
-
119
- * refactor: simplify logic in bec_status_box ([`576353c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/576353cfe8c6fd64db561f0b6e2bc951300643d3))
120
-
121
- ## v0.79.2 (2024-07-04)
122
-
123
- ### Fix
124
-
125
- * fix: overwrite closeEvent and call super class ([`bc0ef78`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/bc0ef7893ef100b71b62101c459655509b534a56))
126
-
127
- ## v0.79.1 (2024-07-03)
128
-
129
- ### Fix
130
-
131
- * fix: use libdir env var to preload Python library, also for Linux platform ([`d7718d4`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/d7718d4dcb9728c050b6421388af4d484f3741f2))
132
-
133
- ## v0.79.0 (2024-07-03)
134
-
135
- ### Feature
136
-
137
- * feat(motor_map_widget): standalone MotorMap Widget with toolbar + plugin ([`6e75642`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6e756420907d7093557e945bc92bc4cfc0138d07))
138
-
139
- * feat(motor_map): method to reset history trace ([`5960918`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/5960918137dd41cdeb94e50f8abc4f169cf45c11))
140
-
141
- ### Fix
142
-
143
- * fix(toolbar): change default color to black to match BECFigure theme ([`b8774e0`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b8774e0b0bc43dcd00f94f42539a778e507ca27d))
144
-
145
- * fix(motor_map): fixed bug with residual trace after changing motors ([`aaa0d10`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/aaa0d1003d2e94b45bafe4f700852c2c05288aea))
PKG-INFO CHANGED
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bec_widgets
3
- Version: 0.83.0
3
+ Version: 0.84.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
@@ -14,9 +14,9 @@ Requires-Dist: bec-lib~=2.16
14
14
  Requires-Dist: black~=24.0
15
15
  Requires-Dist: isort>=5.13.2,~=5.13
16
16
  Requires-Dist: pydantic~=2.0
17
- Requires-Dist: pyqtdarktheme~=2.1
18
17
  Requires-Dist: pyqtgraph~=0.13
19
18
  Requires-Dist: pyte
19
+ Requires-Dist: qdarkstyle>=3.2.2
20
20
  Requires-Dist: qtconsole>=5.5.1,~=5.5
21
21
  Requires-Dist: qtpy~=2.4
22
22
  Provides-Extra: dev
bec_widgets/cli/client.py CHANGED
@@ -19,6 +19,9 @@ class Widgets(str, enum.Enum):
19
19
  BECMotorMapWidget = "BECMotorMapWidget"
20
20
  BECQueue = "BECQueue"
21
21
  BECStatusBox = "BECStatusBox"
22
+ DeviceBox = "DeviceBox"
23
+ DeviceComboBox = "DeviceComboBox"
24
+ DeviceLineEdit = "DeviceLineEdit"
22
25
  RingProgressBar = "RingProgressBar"
23
26
  ScanControl = "ScanControl"
24
27
  StopButton = "StopButton"
@@ -465,8 +468,9 @@ class BECFigure(RPCBase):
465
468
  @rpc_call
466
469
  def plot(
467
470
  self,
468
- x: "list | np.ndarray | None" = None,
471
+ arg1: "list | np.ndarray | str | None" = None,
469
472
  y: "list | np.ndarray | None" = None,
473
+ x: "list | np.ndarray | None" = None,
470
474
  x_name: "str | None" = None,
471
475
  y_name: "str | None" = None,
472
476
  z_name: "str | None" = None,
@@ -488,8 +492,9 @@ class BECFigure(RPCBase):
488
492
  Add a 1D waveform plot to the figure. Always access the first waveform widget in the figure.
489
493
 
490
494
  Args:
491
- x(list | np.ndarray): Custom x data to plot.
495
+ arg1(list | np.ndarray | str | None): First argument which can be x data, y data, or y_name.
492
496
  y(list | np.ndarray): Custom y data to plot.
497
+ x(list | np.ndarray): Custom x data to plot.
493
498
  x_name(str): The name of the device for the x-axis.
494
499
  y_name(str): The name of the device for the y-axis.
495
500
  z_name(str): The name of the device for the z-axis.
@@ -1494,8 +1499,9 @@ class BECWaveform(RPCBase):
1494
1499
  @rpc_call
1495
1500
  def plot(
1496
1501
  self,
1497
- x: "list | np.ndarray | None" = None,
1502
+ arg1: "list | np.ndarray | str | None" = None,
1498
1503
  y: "list | np.ndarray | None" = None,
1504
+ x: "list | np.ndarray | None" = None,
1499
1505
  x_name: "str | None" = None,
1500
1506
  y_name: "str | None" = None,
1501
1507
  z_name: "str | None" = None,
@@ -1507,13 +1513,20 @@ class BECWaveform(RPCBase):
1507
1513
  label: "str | None" = None,
1508
1514
  validate: "bool" = True,
1509
1515
  dap: "str | None" = None,
1516
+ **kwargs,
1510
1517
  ) -> "BECCurve":
1511
1518
  """
1512
1519
  Plot a curve to the plot widget.
1520
+
1513
1521
  Args:
1514
- x(list | np.ndarray): Custom x data to plot.
1522
+ arg1(list | np.ndarray | str | None): First argument which can be x data, y data, or y_name.
1515
1523
  y(list | np.ndarray): Custom y data to plot.
1516
- x_name(str): The name of the device for the x-axis.
1524
+ x(list | np.ndarray): Custom y data to plot.
1525
+ x_name(str): Name of the x signal.
1526
+ - "best_effort": Use the best effort signal.
1527
+ - "timestamp": Use the timestamp signal.
1528
+ - "index": Use the index signal.
1529
+ - Custom signal name of device from BEC.
1517
1530
  y_name(str): The name of the device for the y-axis.
1518
1531
  z_name(str): The name of the device for the z-axis.
1519
1532
  x_entry(str): The name of the entry for the x-axis.
@@ -1523,7 +1536,7 @@ class BECWaveform(RPCBase):
1523
1536
  color_map_z(str): The color map to use for the z-axis.
1524
1537
  label(str): The label of the curve.
1525
1538
  validate(bool): If True, validate the device names and entries.
1526
- dap(str): The dap model to use for the curve. If not specified, none will be added.
1539
+ dap(str): The dap model to use for the curve, only available for sync devices. If not specified, none will be added.
1527
1540
 
1528
1541
  Returns:
1529
1542
  BECCurve: The curve object.
@@ -1532,12 +1545,13 @@ class BECWaveform(RPCBase):
1532
1545
  @rpc_call
1533
1546
  def add_dap(
1534
1547
  self,
1535
- x_name: "str",
1536
- y_name: "str",
1548
+ x_name: "str | None" = None,
1549
+ y_name: "str | None" = None,
1537
1550
  x_entry: "Optional[str]" = None,
1538
1551
  y_entry: "Optional[str]" = None,
1539
1552
  color: "Optional[str]" = None,
1540
1553
  dap: "str" = "GaussianModel",
1554
+ validate_bec: "bool" = True,
1541
1555
  **kwargs,
1542
1556
  ) -> "BECCurve":
1543
1557
  """
@@ -1552,12 +1566,27 @@ class BECWaveform(RPCBase):
1552
1566
  color_map_z(str): The color map to use for the z-axis.
1553
1567
  label(str, optional): Label of the curve. Defaults to None.
1554
1568
  dap(str): The dap model to use for the curve.
1569
+ validate_bec(bool, optional): If True, validate the signal with BEC. Defaults to True.
1555
1570
  **kwargs: Additional keyword arguments for the curve configuration.
1556
1571
 
1557
1572
  Returns:
1558
1573
  BECCurve: The curve object.
1559
1574
  """
1560
1575
 
1576
+ @rpc_call
1577
+ def set_x(self, x_name: "str", x_entry: "str | None" = None):
1578
+ """
1579
+ Change the x axis of the plot widget.
1580
+
1581
+ Args:
1582
+ x_name(str): Name of the x signal.
1583
+ - "best_effort": Use the best effort signal.
1584
+ - "timestamp": Use the timestamp signal.
1585
+ - "index": Use the index signal.
1586
+ - Custom signal name of device from BEC.
1587
+ x_entry(str): Entry of the x signal.
1588
+ """
1589
+
1561
1590
  @rpc_call
1562
1591
  def get_dap_params(self) -> "dict":
1563
1592
  """
@@ -1742,6 +1771,12 @@ class BECWaveform(RPCBase):
1742
1771
  Remove the plot widget from the figure.
1743
1772
  """
1744
1773
 
1774
+ @rpc_call
1775
+ def clear_all(self):
1776
+ """
1777
+ None
1778
+ """
1779
+
1745
1780
  @rpc_call
1746
1781
  def set_legend_label_size(self, size: "int" = None):
1747
1782
  """
@@ -1752,6 +1787,24 @@ class BECWaveform(RPCBase):
1752
1787
  """
1753
1788
 
1754
1789
 
1790
+ class DeviceBox(RPCBase):
1791
+ @property
1792
+ @rpc_call
1793
+ def _config_dict(self) -> "dict":
1794
+ """
1795
+ Get the configuration of the widget.
1796
+
1797
+ Returns:
1798
+ dict: The configuration of the widget.
1799
+ """
1800
+
1801
+ @rpc_call
1802
+ def _get_all_rpc(self) -> "dict":
1803
+ """
1804
+ Get all registered RPC objects.
1805
+ """
1806
+
1807
+
1755
1808
  class DeviceComboBox(RPCBase):
1756
1809
  @property
1757
1810
  @rpc_call
@@ -2,14 +2,20 @@ import os
2
2
 
3
3
  import numpy as np
4
4
  import pyqtgraph as pg
5
- import qdarktheme
6
- from qtconsole.inprocess import QtInProcessKernelManager
7
- from qtconsole.rich_jupyter_widget import RichJupyterWidget
8
5
  from qtpy.QtCore import QSize
9
6
  from qtpy.QtGui import QIcon
10
- from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget
11
-
12
- from bec_widgets.utils import BECDispatcher, UILoader
7
+ from qtpy.QtWidgets import (
8
+ QApplication,
9
+ QGroupBox,
10
+ QHBoxLayout,
11
+ QSplitter,
12
+ QTabWidget,
13
+ QVBoxLayout,
14
+ QWidget,
15
+ )
16
+
17
+ from bec_widgets.utils import BECDispatcher
18
+ from bec_widgets.utils.colors import apply_theme
13
19
  from bec_widgets.widgets.dock.dock_area import BECDockArea
14
20
  from bec_widgets.widgets.figure import BECFigure
15
21
  from bec_widgets.widgets.jupyter_console.jupyter_console import BECJupyterConsole
@@ -21,14 +27,8 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
21
27
  def __init__(self, parent=None):
22
28
  super().__init__(parent)
23
29
 
24
- current_path = os.path.dirname(__file__)
25
- self.ui = UILoader().load_ui(os.path.join(current_path, "jupyter_console_window.ui"), self)
26
-
27
30
  self._init_ui()
28
31
 
29
- self.ui.splitter.setSizes([200, 100])
30
- self.safe_close = False
31
-
32
32
  # console push
33
33
  if self.console.inprocess is True:
34
34
  self.console.kernel_manager.kernel.shell.push(
@@ -40,10 +40,12 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
40
40
  "w1": self.w1,
41
41
  "w2": self.w2,
42
42
  "w3": self.w3,
43
- "w1_c": self.w1_c,
44
- "w2_c": self.w2_c,
45
- "w3_c": self.w3_c,
46
43
  "w4": self.w4,
44
+ "w5": self.w5,
45
+ "w6": self.w6,
46
+ "w7": self.w7,
47
+ "w8": self.w8,
48
+ "w9": self.w9,
47
49
  "d0": self.d0,
48
50
  "d1": self.d1,
49
51
  "d2": self.d2,
@@ -53,14 +55,30 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
53
55
  )
54
56
 
55
57
  def _init_ui(self):
56
- # Plotting window
57
- self.glw_1_layout = QVBoxLayout(self.ui.glw) # Create a new QVBoxLayout
58
- self.figure = BECFigure(parent=self, gui_id="remote") # Create a new BECDeviceMonitor
59
- self.glw_1_layout.addWidget(self.figure) # Add BECDeviceMonitor to the layout
58
+ self.layout = QHBoxLayout(self)
59
+
60
+ # Horizontal splitter
61
+ splitter = QSplitter(self)
62
+ self.layout.addWidget(splitter)
60
63
 
61
- self.dock_layout = QVBoxLayout(self.ui.dock_placeholder)
62
- self.dock = BECDockArea(gui_id="remote")
63
- self.dock_layout.addWidget(self.dock)
64
+ tab_widget = QTabWidget(splitter)
65
+
66
+ first_tab = QWidget()
67
+ first_tab_layout = QVBoxLayout(first_tab)
68
+ self.dock = BECDockArea(gui_id="dock")
69
+ first_tab_layout.addWidget(self.dock)
70
+ tab_widget.addTab(first_tab, "Dock Area")
71
+
72
+ second_tab = QWidget()
73
+ second_tab_layout = QVBoxLayout(second_tab)
74
+ self.figure = BECFigure(parent=self, gui_id="figure")
75
+ second_tab_layout.addWidget(self.figure)
76
+ tab_widget.addTab(second_tab, "BEC Figure")
77
+
78
+ group_box = QGroupBox("Jupyter Console", splitter)
79
+ group_box_layout = QVBoxLayout(group_box)
80
+ self.console = BECJupyterConsole(inprocess=True)
81
+ group_box_layout.addWidget(self.console)
64
82
 
65
83
  # add stuff to figure
66
84
  self._init_figure()
@@ -68,44 +86,70 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
68
86
  # init dock for testing
69
87
  self._init_dock()
70
88
 
71
- self.console_layout = QVBoxLayout(self.ui.widget_console)
72
- self.console = BECJupyterConsole(inprocess=True)
73
- self.console_layout.addWidget(self.console)
89
+ self.setWindowTitle("Jupyter Console Window")
74
90
 
75
91
  def _init_figure(self):
76
- self.figure.plot(x_name="samx", y_name="samy", z_name="bpm4i", color_map_z="cividis")
77
- self.figure.motor_map("samx", "samy")
78
- self.figure.image("eiger", color_map="viridis", vrange=(0, 100))
79
- self.figure.plot(
80
- x_name="samx", y_name="samy", z_name="bpm4i", color_map_z="magma", new=True
92
+ self.w1 = self.figure.plot(
93
+ x_name="samx",
94
+ y_name="bpm4i",
95
+ # title="Standard Plot with sync device, custom labels - w1",
96
+ # x_label="Motor Position",
97
+ # y_label="Intensity (A.U.)",
98
+ row=0,
99
+ col=0,
100
+ )
101
+ self.w1.set(
102
+ title="Standard Plot with sync device, custom labels - w1",
103
+ x_label="Motor Position",
104
+ y_label="Intensity (A.U.)",
105
+ )
106
+ self.w2 = self.figure.motor_map("samx", "samy", row=0, col=1)
107
+ self.w3 = self.figure.image(
108
+ "eiger", color_map="viridis", vrange=(0, 100), title="Eiger Image - w3", row=0, col=2
109
+ )
110
+ self.w4 = self.figure.plot(
111
+ x_name="samx",
112
+ y_name="samy",
113
+ z_name="bpm4i",
114
+ color_map_z="magma",
115
+ new=True,
116
+ title="2D scatter plot - w4",
117
+ row=0,
118
+ col=3,
119
+ )
120
+ self.w5 = self.figure.plot(
121
+ y_name="bpm4i",
122
+ new=True,
123
+ title="Best Effort Plot - w5",
124
+ dap="GaussianModel",
125
+ row=1,
126
+ col=0,
127
+ )
128
+ self.w6 = self.figure.plot(
129
+ x_name="timestamp", y_name="bpm4i", new=True, title="Timestamp Plot - w6", row=1, col=1
130
+ )
131
+ self.w7 = self.figure.plot(
132
+ x_name="index", y_name="bpm4i", new=True, title="Index Plot - w7", row=1, col=2
133
+ )
134
+ self.w8 = self.figure.plot(
135
+ y_name="monitor_async", new=True, title="Async Plot - Best Effort - w8", row=2, col=0
136
+ )
137
+ self.w9 = self.figure.plot(
138
+ x_name="timestamp",
139
+ y_name="monitor_async",
140
+ new=True,
141
+ title="Async Plot - timestamp - w9",
142
+ row=2,
143
+ col=1,
144
+ )
145
+ self.w10 = self.figure.plot(
146
+ x_name="index",
147
+ y_name="monitor_async",
148
+ new=True,
149
+ title="Async Plot - index - w10",
150
+ row=2,
151
+ col=2,
81
152
  )
82
-
83
- self.figure.change_layout(2, 2)
84
-
85
- self.w1 = self.figure[0, 0]
86
- self.w2 = self.figure[0, 1]
87
- self.w3 = self.figure[1, 0]
88
- self.w4 = self.figure[1, 1]
89
-
90
- # Plot Customisation
91
- self.w1.set_title("Waveform 1")
92
- self.w1.set_x_label("Motor Position (samx)")
93
- self.w1.set_y_label("Intensity A.U.")
94
-
95
- # Image Customisation
96
- self.w3.set_title("Eiger Image")
97
- self.w3.set_x_label("X")
98
- self.w3.set_y_label("Y")
99
-
100
- # Configs to try to pass
101
- self.w1_c = self.w1._config_dict
102
- self.w2_c = self.w2._config_dict
103
- self.w3_c = self.w3._config_dict
104
-
105
- # curves for w1
106
- self.c1 = self.w1.get_config()
107
-
108
- self.fig_c = self.figure._config_dict
109
153
 
110
154
  def _init_dock(self):
111
155
 
@@ -131,9 +175,13 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
131
175
 
132
176
  def closeEvent(self, event):
133
177
  """Override to handle things when main window is closed."""
178
+ self.dock.clear_all()
134
179
  self.dock.cleanup()
180
+ self.dock.close()
135
181
  self.figure.clear_all()
136
- self.figure.client.shutdown()
182
+ self.figure.cleanup()
183
+ self.figure.close()
184
+
137
185
  super().closeEvent(event)
138
186
 
139
187
 
@@ -147,7 +195,7 @@ if __name__ == "__main__": # pragma: no cover
147
195
  app = QApplication(sys.argv)
148
196
  app.setApplicationName("Jupyter Console")
149
197
  app.setApplicationDisplayName("Jupyter Console")
150
- qdarktheme.setup_theme("auto")
198
+ apply_theme("dark")
151
199
  icon = QIcon()
152
200
  icon.addFile(os.path.join(module_path, "assets", "terminal_icon.png"), size=QSize(48, 48))
153
201
  app.setWindowIcon(icon)
@@ -26,7 +26,9 @@ class ModularToolBar(QToolBar):
26
26
  color (str, optional): The background color of the toolbar. Defaults to "black".
27
27
  """
28
28
 
29
- def __init__(self, parent=None, actions=None, target_widget=None, color: str = "black"):
29
+ def __init__(
30
+ self, parent=None, actions=None, target_widget=None, color: str = "rgba(255, 255, 255, 0)"
31
+ ):
30
32
  super().__init__(parent)
31
33
 
32
34
  self.widgets = defaultdict(dict)
@@ -134,7 +134,10 @@ class BECDispatcher:
134
134
  cls.qapp = None
135
135
 
136
136
  def connect_slot(
137
- self, slot: Callable, topics: Union[EndpointInfo, str, list[Union[EndpointInfo, str]]]
137
+ self,
138
+ slot: Callable,
139
+ topics: Union[EndpointInfo, str, list[Union[EndpointInfo, str]]],
140
+ **kwargs,
138
141
  ) -> None:
139
142
  """Connect widget's pyqt slot, so that it is called on new pub/sub topic message.
140
143
 
@@ -144,7 +147,7 @@ class BECDispatcher:
144
147
  topics (EndpointInfo | str | list): A topic or list of topics that can typically be acquired via bec_lib.MessageEndpoints
145
148
  """
146
149
  slot = QtThreadSafeCallback(slot)
147
- self.client.connector.register(topics, cb=slot)
150
+ self.client.connector.register(topics, cb=slot, **kwargs)
148
151
  topics_str, _ = self.client.connector._convert_endpointinfo(topics)
149
152
  self._slots[slot].update(set(topics_str))
150
153
 
@@ -1,10 +1,37 @@
1
+ import itertools
1
2
  import re
2
3
  from typing import Literal
3
4
 
4
5
  import numpy as np
5
6
  import pyqtgraph as pg
7
+ import qdarkstyle
6
8
  from pydantic_core import PydanticCustomError
9
+ from qdarkstyle import DarkPalette, LightPalette
7
10
  from qtpy.QtGui import QColor
11
+ from qtpy.QtWidgets import QApplication
12
+
13
+ CURRENT_THEME = "dark"
14
+
15
+
16
+ def get_theme_palette():
17
+ return DarkPalette if CURRENT_THEME == "dark" else LightPalette
18
+
19
+
20
+ def apply_theme(theme: Literal["dark", "light"]):
21
+ global CURRENT_THEME
22
+ CURRENT_THEME = theme
23
+
24
+ app = QApplication.instance()
25
+ # go through all pyqtgraph widgets and set background
26
+ children = itertools.chain.from_iterable(
27
+ top.findChildren(pg.GraphicsLayoutWidget) for top in app.topLevelWidgets()
28
+ )
29
+ for pg_widget in children:
30
+ pg_widget.setBackground("k" if theme == "dark" else "w")
31
+
32
+ # now define stylesheet according to theme and apply it
33
+ style = qdarkstyle.load_stylesheet(palette=get_theme_palette())
34
+ app.setStyleSheet(style)
8
35
 
9
36
 
10
37
  class Colors:
@@ -9,12 +9,12 @@ from collections import defaultdict
9
9
  from dataclasses import dataclass
10
10
  from typing import TYPE_CHECKING
11
11
 
12
- import qdarktheme
13
12
  from bec_lib.utils.import_utils import lazy_import_from
14
13
  from qtpy.QtCore import QObject, QTimer, Signal, Slot
15
14
  from qtpy.QtWidgets import QHBoxLayout, QTreeWidget, QTreeWidgetItem, QWidget
16
15
 
17
16
  from bec_widgets.utils.bec_connector import BECConnector
17
+ from bec_widgets.utils.colors import apply_theme
18
18
  from bec_widgets.widgets.bec_status_box.status_item import StatusItem
19
19
 
20
20
  if TYPE_CHECKING:
@@ -306,7 +306,7 @@ def main():
306
306
  from qtpy.QtWidgets import QApplication
307
307
 
308
308
  app = QApplication(sys.argv)
309
- qdarktheme.setup_theme("auto")
309
+ apply_theme("dark")
310
310
  main_window = BECStatusBox()
311
311
  main_window.show()
312
312
  sys.exit(app.exec())
@@ -9,6 +9,7 @@ from qtpy.QtWidgets import QDoubleSpinBox, QVBoxLayout, QWidget
9
9
 
10
10
  from bec_widgets.utils import UILoader
11
11
  from bec_widgets.utils.bec_connector import BECConnector
12
+ from bec_widgets.utils.colors import apply_theme
12
13
 
13
14
 
14
15
  class DeviceBox(BECConnector, QWidget):
@@ -186,11 +187,10 @@ class DeviceBox(BECConnector, QWidget):
186
187
  if __name__ == "__main__": # pragma: no cover
187
188
  import sys
188
189
 
189
- import qdarktheme
190
190
  from qtpy.QtWidgets import QApplication
191
191
 
192
192
  app = QApplication(sys.argv)
193
- qdarktheme.setup_theme("light")
193
+ apply_theme("light")
194
194
  widget = DeviceBox(device="samx")
195
195
 
196
196
  widget.show()