bec-widgets 0.87.0__py3-none-any.whl → 0.88.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 (77) hide show
  1. CHANGELOG.md +66 -70
  2. PKG-INFO +1 -1
  3. bec_widgets/assets/designer_icons/BECWaveformWidget.png +0 -0
  4. bec_widgets/assets/designer_icons/colormap_selector.png +0 -0
  5. bec_widgets/assets/toolbar_icons/add.svg +3 -0
  6. bec_widgets/assets/toolbar_icons/auto_range.svg +3 -0
  7. bec_widgets/assets/toolbar_icons/drag_pan_mode.svg +3 -0
  8. bec_widgets/assets/toolbar_icons/export.svg +9 -0
  9. bec_widgets/assets/toolbar_icons/fitting_parameters.svg +3 -0
  10. bec_widgets/assets/toolbar_icons/import.svg +9 -0
  11. bec_widgets/assets/toolbar_icons/line_axis.svg +3 -0
  12. bec_widgets/assets/toolbar_icons/photo_library.svg +3 -0
  13. bec_widgets/assets/toolbar_icons/rectangle_mode.svg +3 -0
  14. bec_widgets/assets/toolbar_icons/remove.svg +5 -0
  15. bec_widgets/assets/toolbar_icons/save.svg +3 -0
  16. bec_widgets/assets/toolbar_icons/settings.svg +4 -0
  17. bec_widgets/cli/client.py +307 -5
  18. bec_widgets/cli/server.py +2 -1
  19. bec_widgets/examples/jupyter_console/jupyter_console_window.py +13 -7
  20. bec_widgets/examples/plugin_example_pyside/tictactoe.py +1 -0
  21. bec_widgets/utils/bec_connector.py +19 -18
  22. bec_widgets/utils/bec_widget.py +19 -4
  23. bec_widgets/widgets/base_classes/device_input_base.py +3 -5
  24. bec_widgets/widgets/bec_queue/bec_queue.py +3 -2
  25. bec_widgets/widgets/bec_status_box/bec_status_box.py +2 -11
  26. bec_widgets/widgets/{color_map_selector/color_map_selector.py → colormap_selector/colormap_selector.py} +3 -1
  27. bec_widgets/widgets/colormap_selector/colormap_selector.pyproject +1 -0
  28. bec_widgets/widgets/{color_map_selector/color_map_selector_plugin.py → colormap_selector/colormap_selector_plugin.py} +8 -6
  29. bec_widgets/widgets/{color_map_selector/register_color_map_selector.py → colormap_selector/register_colormap_selector.py} +1 -1
  30. bec_widgets/widgets/device_box/device_box.py +2 -2
  31. bec_widgets/widgets/device_combobox/device_combobox.py +1 -8
  32. bec_widgets/widgets/device_line_edit/device_line_edit.py +2 -9
  33. bec_widgets/widgets/dock/dock.py +8 -6
  34. bec_widgets/widgets/dock/dock_area.py +4 -2
  35. bec_widgets/widgets/figure/figure.py +16 -8
  36. bec_widgets/widgets/figure/plots/axis_settings.py +38 -11
  37. bec_widgets/widgets/figure/plots/image/image.py +2 -4
  38. bec_widgets/widgets/figure/plots/motor_map/motor_map.py +1 -1
  39. bec_widgets/widgets/figure/plots/plot_base.py +7 -8
  40. bec_widgets/widgets/figure/plots/waveform/waveform.py +49 -53
  41. bec_widgets/widgets/figure/plots/waveform/waveform_curve.py +10 -1
  42. bec_widgets/widgets/jupyter_console/jupyter_console.py +6 -0
  43. bec_widgets/widgets/motor_map/bec_motor_map_widget_plugin.py +1 -1
  44. bec_widgets/widgets/motor_map/motor_map_widget.py +9 -6
  45. bec_widgets/widgets/ring_progress_bar/ring.py +0 -4
  46. bec_widgets/widgets/ring_progress_bar/ring_progress_bar.py +8 -8
  47. bec_widgets/widgets/scan_control/scan_control.py +2 -6
  48. bec_widgets/widgets/stop_button/stop_button.py +2 -2
  49. bec_widgets/widgets/text_box/text_box.py +3 -2
  50. bec_widgets/widgets/waveform/__init__.py +0 -0
  51. bec_widgets/widgets/waveform/bec_waveform_widget.pyproject +1 -0
  52. bec_widgets/widgets/waveform/bec_waveform_widget_plugin.py +59 -0
  53. bec_widgets/widgets/waveform/register_bec_waveform_widget.py +15 -0
  54. bec_widgets/widgets/waveform/waveform_toolbar/__init__.py +0 -0
  55. bec_widgets/widgets/waveform/waveform_toolbar/curve_dialog/__init__.py +0 -0
  56. bec_widgets/widgets/waveform/waveform_toolbar/curve_dialog/curve_dialog.py +341 -0
  57. bec_widgets/widgets/waveform/waveform_toolbar/curve_dialog/curve_dialog.ui +358 -0
  58. bec_widgets/widgets/waveform/waveform_toolbar/dap_summary_dialog/__init__.py +0 -0
  59. bec_widgets/widgets/waveform/waveform_toolbar/dap_summary_dialog/dap_summary.ui +127 -0
  60. bec_widgets/widgets/waveform/waveform_toolbar/dap_summary_dialog/dap_summary_dialog.py +69 -0
  61. bec_widgets/widgets/waveform/waveform_toolbar/waveform_toolbar.py +117 -0
  62. bec_widgets/widgets/waveform/waveform_widget.py +571 -0
  63. bec_widgets/widgets/website/website.py +2 -2
  64. {bec_widgets-0.87.0.dist-info → bec_widgets-0.88.0.dist-info}/METADATA +1 -1
  65. {bec_widgets-0.87.0.dist-info → bec_widgets-0.88.0.dist-info}/RECORD +75 -48
  66. pyproject.toml +1 -1
  67. tests/unit_tests/test_color_map_selector.py +1 -1
  68. tests/unit_tests/test_device_input_base.py +10 -4
  69. tests/unit_tests/test_waveform_widget.py +264 -0
  70. bec_widgets/widgets/color_map_selector/assets/color_map_selector_icon.png +0 -0
  71. bec_widgets/widgets/color_map_selector/color_map_selector.pyproject +0 -1
  72. /bec_widgets/assets/{bec_widgets_icon.png → app_icons/bec_widgets_icon.png} +0 -0
  73. /bec_widgets/assets/{terminal_icon.png → app_icons/terminal_icon.png} +0 -0
  74. /bec_widgets/widgets/{color_map_selector → colormap_selector}/__init__.py +0 -0
  75. {bec_widgets-0.87.0.dist-info → bec_widgets-0.88.0.dist-info}/WHEEL +0 -0
  76. {bec_widgets-0.87.0.dist-info → bec_widgets-0.88.0.dist-info}/entry_points.txt +0 -0
  77. {bec_widgets-0.87.0.dist-info → bec_widgets-0.88.0.dist-info}/licenses/LICENSE +0 -0
bec_widgets/cli/client.py CHANGED
@@ -19,6 +19,7 @@ class Widgets(str, enum.Enum):
19
19
  BECMotorMapWidget = "BECMotorMapWidget"
20
20
  BECQueue = "BECQueue"
21
21
  BECStatusBox = "BECStatusBox"
22
+ BECWaveformWidget = "BECWaveformWidget"
22
23
  DeviceBox = "DeviceBox"
23
24
  DeviceComboBox = "DeviceComboBox"
24
25
  DeviceLineEdit = "DeviceLineEdit"
@@ -184,7 +185,7 @@ class BECDock(RPCBase):
184
185
 
185
186
  @property
186
187
  @rpc_call
187
- def widget_list(self) -> "list[BECConnector]":
188
+ def widget_list(self) -> "list[BECWidget]":
188
189
  """
189
190
  Get the widgets in the dock.
190
191
 
@@ -225,13 +226,13 @@ class BECDock(RPCBase):
225
226
  @rpc_call
226
227
  def add_widget(
227
228
  self,
228
- widget: "BECConnector | str",
229
+ widget: "BECWidget | str",
229
230
  row=None,
230
231
  col=0,
231
232
  rowspan=1,
232
233
  colspan=1,
233
234
  shift: "Literal['down', 'up', 'left', 'right']" = "down",
234
- ) -> "BECConnector":
235
+ ) -> "BECWidget":
235
236
  """
236
237
  Add a widget to the dock.
237
238
 
@@ -614,6 +615,12 @@ class BECFigure(RPCBase):
614
615
  theme(Literal["dark","light"]): The theme to set for the figure widget.
615
616
  """
616
617
 
618
+ @rpc_call
619
+ def export(self):
620
+ """
621
+ Export the plot widget.
622
+ """
623
+
617
624
  @rpc_call
618
625
  def clear_all(self):
619
626
  """
@@ -1096,6 +1103,12 @@ class BECImageShow(RPCBase):
1096
1103
  lock(bool): True to lock, False to unlock.
1097
1104
  """
1098
1105
 
1106
+ @rpc_call
1107
+ def export(self):
1108
+ """
1109
+ Show the Export Dialog of the plot widget.
1110
+ """
1111
+
1099
1112
  @rpc_call
1100
1113
  def remove(self):
1101
1114
  """
@@ -1204,6 +1217,12 @@ class BECMotorMap(RPCBase):
1204
1217
  dict: Data of the motor map.
1205
1218
  """
1206
1219
 
1220
+ @rpc_call
1221
+ def export(self):
1222
+ """
1223
+ Show the Export Dialog of the plot widget.
1224
+ """
1225
+
1207
1226
  @rpc_call
1208
1227
  def remove(self):
1209
1228
  """
@@ -1298,6 +1317,12 @@ class BECMotorMapWidget(RPCBase):
1298
1317
  Reset the history of the motor map.
1299
1318
  """
1300
1319
 
1320
+ @rpc_call
1321
+ def export(self):
1322
+ """
1323
+ Show the export dialog for the motor map.
1324
+ """
1325
+
1301
1326
 
1302
1327
  class BECPlotBase(RPCBase):
1303
1328
  @property
@@ -1426,6 +1451,12 @@ class BECPlotBase(RPCBase):
1426
1451
  lock(bool): True to lock, False to unlock.
1427
1452
  """
1428
1453
 
1454
+ @rpc_call
1455
+ def export(self):
1456
+ """
1457
+ Show the Export Dialog of the plot widget.
1458
+ """
1459
+
1429
1460
  @rpc_call
1430
1461
  def remove(self):
1431
1462
  """
@@ -1563,8 +1594,6 @@ class BECWaveform(RPCBase):
1563
1594
  y_name(str): Name of the y signal.
1564
1595
  y_entry(str): Entry of the y signal.
1565
1596
  color(str, optional): Color of the curve. Defaults to None.
1566
- color_map_z(str): The color map to use for the z-axis.
1567
- label(str, optional): Label of the curve. Defaults to None.
1568
1597
  dap(str): The dap model to use for the curve.
1569
1598
  validate_bec(bool, optional): If True, validate the signal with BEC. Defaults to True.
1570
1599
  **kwargs: Additional keyword arguments for the curve configuration.
@@ -1756,6 +1785,15 @@ class BECWaveform(RPCBase):
1756
1785
  y(bool): Show grid on the y-axis.
1757
1786
  """
1758
1787
 
1788
+ @rpc_call
1789
+ def set_colormap(self, colormap: "str | None" = None):
1790
+ """
1791
+ Set the colormap of the plot widget.
1792
+
1793
+ Args:
1794
+ colormap(str, optional): Scale the colors of curves to colormap. If None, use the default color palette.
1795
+ """
1796
+
1759
1797
  @rpc_call
1760
1798
  def lock_aspect_ratio(self, lock):
1761
1799
  """
@@ -1765,6 +1803,12 @@ class BECWaveform(RPCBase):
1765
1803
  lock(bool): True to lock, False to unlock.
1766
1804
  """
1767
1805
 
1806
+ @rpc_call
1807
+ def export(self):
1808
+ """
1809
+ Show the Export Dialog of the plot widget.
1810
+ """
1811
+
1768
1812
  @rpc_call
1769
1813
  def remove(self):
1770
1814
  """
@@ -1787,6 +1831,264 @@ class BECWaveform(RPCBase):
1787
1831
  """
1788
1832
 
1789
1833
 
1834
+ class BECWaveformWidget(RPCBase):
1835
+ @property
1836
+ @rpc_call
1837
+ def curves(self) -> "list[BECCurve]":
1838
+ """
1839
+ Get the curves of the plot widget as a list
1840
+ Returns:
1841
+ list: List of curves.
1842
+ """
1843
+
1844
+ @rpc_call
1845
+ def plot(
1846
+ self,
1847
+ arg1: "list | np.ndarray | str | None" = None,
1848
+ x: "list | np.ndarray | None" = None,
1849
+ y: "list | np.ndarray | None" = None,
1850
+ x_name: "str | None" = None,
1851
+ y_name: "str | None" = None,
1852
+ z_name: "str | None" = None,
1853
+ x_entry: "str | None" = None,
1854
+ y_entry: "str | None" = None,
1855
+ z_entry: "str | None" = None,
1856
+ color: "str | None" = None,
1857
+ color_map_z: "str | None" = "plasma",
1858
+ label: "str | None" = None,
1859
+ validate: "bool" = True,
1860
+ dap: "str | None" = None,
1861
+ **kwargs,
1862
+ ) -> "BECCurve":
1863
+ """
1864
+ Plot a curve to the plot widget.
1865
+ Args:
1866
+ arg1(list | np.ndarray | str | None): First argument which can be x data(list | np.ndarray), y data(list | np.ndarray), or y_name(str).
1867
+ x(list | np.ndarray): Custom x data to plot.
1868
+ y(list | np.ndarray): Custom y data to plot.
1869
+ x_name(str): The name of the device for the x-axis.
1870
+ y_name(str): The name of the device for the y-axis.
1871
+ z_name(str): The name of the device for the z-axis.
1872
+ x_entry(str): The name of the entry for the x-axis.
1873
+ y_entry(str): The name of the entry for the y-axis.
1874
+ z_entry(str): The name of the entry for the z-axis.
1875
+ color(str): The color of the curve.
1876
+ color_map_z(str): The color map to use for the z-axis.
1877
+ label(str): The label of the curve.
1878
+ validate(bool): If True, validate the device names and entries.
1879
+ dap(str): The dap model to use for the curve. If not specified, none will be added.
1880
+
1881
+ Returns:
1882
+ BECCurve: The curve object.
1883
+ """
1884
+
1885
+ @rpc_call
1886
+ def add_dap(
1887
+ self,
1888
+ x_name: "str",
1889
+ y_name: "str",
1890
+ x_entry: "str | None" = None,
1891
+ y_entry: "str | None" = None,
1892
+ color: "str | None" = None,
1893
+ dap: "str" = "GaussianModel",
1894
+ validate_bec: "bool" = True,
1895
+ **kwargs,
1896
+ ) -> "BECCurve":
1897
+ """
1898
+ Add LMFIT dap model curve to the plot widget.
1899
+
1900
+ Args:
1901
+ x_name(str): Name of the x signal.
1902
+ x_entry(str): Entry of the x signal.
1903
+ y_name(str): Name of the y signal.
1904
+ y_entry(str): Entry of the y signal.
1905
+ color(str, optional): Color of the curve. Defaults to None.
1906
+ dap(str): The dap model to use for the curve.
1907
+ validate_bec(bool, optional): If True, validate the signal with BEC. Defaults to True.
1908
+ **kwargs: Additional keyword arguments for the curve configuration.
1909
+
1910
+ Returns:
1911
+ BECCurve: The curve object.
1912
+ """
1913
+
1914
+ @rpc_call
1915
+ def get_dap_params(self) -> "dict":
1916
+ """
1917
+ Get the DAP parameters of all DAP curves.
1918
+
1919
+ Returns:
1920
+ dict: DAP parameters of all DAP curves.
1921
+ """
1922
+
1923
+ @rpc_call
1924
+ def remove_curve(self, *identifiers):
1925
+ """
1926
+ Remove a curve from the plot widget.
1927
+
1928
+ Args:
1929
+ *identifiers: Identifier of the curve to be removed. Can be either an integer (index) or a string (curve_id).
1930
+ """
1931
+
1932
+ @rpc_call
1933
+ def scan_history(self, scan_index: "int" = None, scan_id: "str" = None):
1934
+ """
1935
+ Update the scan curves with the data from the scan storage.
1936
+ Provide only one of scan_id or scan_index.
1937
+
1938
+ Args:
1939
+ scan_id(str, optional): ScanID of the scan to be updated. Defaults to None.
1940
+ scan_index(int, optional): Index of the scan to be updated. Defaults to None.
1941
+ """
1942
+
1943
+ @rpc_call
1944
+ def get_all_data(self, output: "Literal['dict', 'pandas']" = "dict") -> "dict | pd.DataFrame":
1945
+ """
1946
+ Extract all curve data into a dictionary or a pandas DataFrame.
1947
+
1948
+ Args:
1949
+ output (Literal["dict", "pandas"]): Format of the output data.
1950
+
1951
+ Returns:
1952
+ dict | pd.DataFrame: Data of all curves in the specified format.
1953
+ """
1954
+
1955
+ @rpc_call
1956
+ def set(self, **kwargs):
1957
+ """
1958
+ Set the properties of the plot widget.
1959
+
1960
+ Args:
1961
+ **kwargs: Keyword arguments for the properties to be set.
1962
+
1963
+ Possible properties:
1964
+ - title: str
1965
+ - x_label: str
1966
+ - y_label: str
1967
+ - x_scale: Literal["linear", "log"]
1968
+ - y_scale: Literal["linear", "log"]
1969
+ - x_lim: tuple
1970
+ - y_lim: tuple
1971
+ - legend_label_size: int
1972
+ """
1973
+
1974
+ @rpc_call
1975
+ def set_x(self, x_name: "str", x_entry: "str | None" = None):
1976
+ """
1977
+ Change the x axis of the plot widget.
1978
+
1979
+ Args:
1980
+ x_name(str): Name of the x signal.
1981
+ - "best_effort": Use the best effort signal.
1982
+ - "timestamp": Use the timestamp signal.
1983
+ - "index": Use the index signal.
1984
+ - Custom signal name of device from BEC.
1985
+ x_entry(str): Entry of the x signal.
1986
+ """
1987
+
1988
+ @rpc_call
1989
+ def set_title(self, title: "str"):
1990
+ """
1991
+ Set the title of the plot widget.
1992
+
1993
+ Args:
1994
+ title(str): Title of the plot.
1995
+ """
1996
+
1997
+ @rpc_call
1998
+ def set_x_label(self, x_label: "str"):
1999
+ """
2000
+ Set the x-axis label of the plot widget.
2001
+
2002
+ Args:
2003
+ x_label(str): Label of the x-axis.
2004
+ """
2005
+
2006
+ @rpc_call
2007
+ def set_y_label(self, y_label: "str"):
2008
+ """
2009
+ Set the y-axis label of the plot widget.
2010
+
2011
+ Args:
2012
+ y_label(str): Label of the y-axis.
2013
+ """
2014
+
2015
+ @rpc_call
2016
+ def set_x_scale(self, x_scale: "Literal['linear', 'log']"):
2017
+ """
2018
+ Set the scale of the x-axis of the plot widget.
2019
+
2020
+ Args:
2021
+ x_scale(Literal["linear", "log"]): Scale of the x-axis.
2022
+ """
2023
+
2024
+ @rpc_call
2025
+ def set_y_scale(self, y_scale: "Literal['linear', 'log']"):
2026
+ """
2027
+ Set the scale of the y-axis of the plot widget.
2028
+
2029
+ Args:
2030
+ y_scale(Literal["linear", "log"]): Scale of the y-axis.
2031
+ """
2032
+
2033
+ @rpc_call
2034
+ def set_x_lim(self, x_lim: "tuple"):
2035
+ """
2036
+ Set the limits of the x-axis of the plot widget.
2037
+
2038
+ Args:
2039
+ x_lim(tuple): Limits of the x-axis.
2040
+ """
2041
+
2042
+ @rpc_call
2043
+ def set_y_lim(self, y_lim: "tuple"):
2044
+ """
2045
+ Set the limits of the y-axis of the plot widget.
2046
+
2047
+ Args:
2048
+ y_lim(tuple): Limits of the y-axis.
2049
+ """
2050
+
2051
+ @rpc_call
2052
+ def set_legend_label_size(self, legend_label_size: "int"):
2053
+ """
2054
+ Set the size of the legend labels of the plot widget.
2055
+
2056
+ Args:
2057
+ legend_label_size(int): Size of the legend labels.
2058
+ """
2059
+
2060
+ @rpc_call
2061
+ def set_grid(self, x_grid: "bool", y_grid: "bool"):
2062
+ """
2063
+ Set the grid visibility of the plot widget.
2064
+
2065
+ Args:
2066
+ x_grid(bool): Visibility of the x-axis grid.
2067
+ y_grid(bool): Visibility of the y-axis grid.
2068
+ """
2069
+
2070
+ @rpc_call
2071
+ def lock_aspect_ratio(self, lock: "bool"):
2072
+ """
2073
+ Lock the aspect ratio of the plot widget.
2074
+
2075
+ Args:
2076
+ lock(bool): Lock the aspect ratio.
2077
+ """
2078
+
2079
+ @rpc_call
2080
+ def export(self):
2081
+ """
2082
+ Show the export dialog for the plot widget.
2083
+ """
2084
+
2085
+ @rpc_call
2086
+ def export_to_matplotlib(self):
2087
+ """
2088
+ Export the plot widget to Matplotlib.
2089
+ """
2090
+
2091
+
1790
2092
  class DeviceBox(RPCBase):
1791
2093
  @property
1792
2094
  @rpc_call
bec_widgets/cli/server.py CHANGED
@@ -202,7 +202,8 @@ def main():
202
202
  module_path = os.path.dirname(bec_widgets.__file__)
203
203
  icon = QIcon()
204
204
  icon.addFile(
205
- os.path.join(module_path, "assets", "bec_widgets_icon.png"), size=QSize(48, 48)
205
+ os.path.join(module_path, "assets", "app_icons", "bec_widgets_icon.png"),
206
+ size=QSize(48, 48),
206
207
  )
207
208
  app.setWindowIcon(icon)
208
209
 
@@ -49,8 +49,9 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
49
49
  "d0": self.d0,
50
50
  "d1": self.d1,
51
51
  "d2": self.d2,
52
- "plt": self.plt,
52
+ "wave": self.wave,
53
53
  "bar": self.bar,
54
+ "cm": self.colormap,
54
55
  }
55
56
  )
56
57
 
@@ -165,22 +166,25 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
165
166
  self.fig1.plot(x_name="samx", y_name="bpm3a")
166
167
 
167
168
  self.d2 = self.dock.add_dock(name="dock_2", position="bottom")
168
- self.fig2 = self.d2.add_widget("BECFigure", row=0, col=0)
169
- self.plt = self.fig2.plot(x_name="samx", y_name="bpm3a")
170
- self.plt.plot(x_name="samx", y_name="bpm4i", dap="GaussianModel")
169
+ self.wave = self.d2.add_widget("BECWaveformWidget", row=0, col=0)
170
+ # self.wave.plot(x_name="samx", y_name="bpm3a")
171
+ # self.wave.plot(x_name="samx", y_name="bpm4i", dap="GaussianModel")
171
172
  self.bar = self.d2.add_widget("RingProgressBar", row=0, col=1)
172
173
  self.bar.set_diameter(200)
173
174
 
175
+ self.d3 = self.dock.add_dock(name="dock_3", position="bottom")
176
+ self.colormap = pg.GradientWidget()
177
+ self.d3.add_widget(self.colormap, row=0, col=0)
178
+
174
179
  self.dock.save_state()
175
180
 
176
181
  def closeEvent(self, event):
177
182
  """Override to handle things when main window is closed."""
178
- self.dock.clear_all()
179
183
  self.dock.cleanup()
180
184
  self.dock.close()
181
- self.figure.clear_all()
182
185
  self.figure.cleanup()
183
186
  self.figure.close()
187
+ self.console.close()
184
188
 
185
189
  super().closeEvent(event)
186
190
 
@@ -197,7 +201,9 @@ if __name__ == "__main__": # pragma: no cover
197
201
  app.setApplicationDisplayName("Jupyter Console")
198
202
  apply_theme("dark")
199
203
  icon = QIcon()
200
- icon.addFile(os.path.join(module_path, "assets", "terminal_icon.png"), size=QSize(48, 48))
204
+ icon.addFile(
205
+ os.path.join(module_path, "assets", "app_icons", "terminal_icon.png"), size=QSize(48, 48)
206
+ )
201
207
  app.setWindowIcon(icon)
202
208
 
203
209
  bec_dispatcher = BECDispatcher()
@@ -16,6 +16,7 @@ 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 !!!!!!")
19
20
 
20
21
  def minimumSizeHint(self):
21
22
  return QSize(200, 200)
@@ -11,10 +11,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
13
13
  from qtpy.QtCore import Slot as pyqtSlot
14
+ from qtpy.QtWidgets import QApplication
14
15
 
15
16
  from bec_widgets.cli.rpc_register import RPCRegister
16
17
  from bec_widgets.qt_utils.error_popups import ErrorPopupUtility
17
- from bec_widgets.utils.bec_widget import BECWidget
18
18
  from bec_widgets.utils.yaml_dialog import load_yaml, load_yaml_gui, save_yaml, save_yaml_gui
19
19
 
20
20
  BECDispatcher = lazy_import_from("bec_widgets.utils.bec_dispatcher", ("BECDispatcher",))
@@ -65,16 +65,30 @@ class Worker(QRunnable):
65
65
  self.signals.completed.emit()
66
66
 
67
67
 
68
- class BECConnector(BECWidget):
69
- """Connection mixin class for all BEC widgets, to handle BEC client and device manager"""
68
+ class BECConnector:
69
+ """Connection mixin class to handle BEC client and device manager"""
70
70
 
71
71
  USER_ACCESS = ["_config_dict", "_get_all_rpc"]
72
+ EXIT_HANDLERS = {}
72
73
 
73
74
  def __init__(self, client=None, config: ConnectionConfig = None, gui_id: str = None):
74
75
  # BEC related connections
75
76
  self.bec_dispatcher = BECDispatcher(client=client)
76
77
  self.client = self.bec_dispatcher.client if client is None else client
77
78
 
79
+ if not self.client in BECConnector.EXIT_HANDLERS:
80
+ # register function to clean connections at exit;
81
+ # the function depends on BECClient, and BECDispatcher
82
+ @pyqtSlot()
83
+ def terminate(client=self.client, dispatcher=self.bec_dispatcher):
84
+ print("Disconnecting", repr(dispatcher))
85
+ dispatcher.disconnect_all()
86
+ print("Shutting down BEC Client", repr(client))
87
+ client.shutdown()
88
+
89
+ BECConnector.EXIT_HANDLERS[self.client] = terminate
90
+ QApplication.instance().aboutToQuit.connect(terminate)
91
+
78
92
  if config:
79
93
  self.config = config
80
94
  self.config.widget_class = self.__class__.__name__
@@ -92,6 +106,8 @@ class BECConnector(BECWidget):
92
106
  self.gui_id = self.config.gui_id
93
107
 
94
108
  # register widget to rpc register
109
+ # be careful: when registering, and the object is not a BECWidget,
110
+ # cleanup has to called manually since there is no 'closeEvent'
95
111
  self.rpc_register = RPCRegister()
96
112
  self.rpc_register.add_rpc(self)
97
113
 
@@ -284,18 +300,3 @@ class BECConnector(BECWidget):
284
300
  return self.config.model_dump()
285
301
  else:
286
302
  return self.config
287
-
288
- def cleanup(self):
289
- """Cleanup the widget."""
290
- self.rpc_register.remove_rpc(self)
291
- all_connections = self.rpc_register.list_all_connections()
292
- if len(all_connections) == 0:
293
- print("No more connections. Shutting down GUI BEC client.")
294
- self.bec_dispatcher.disconnect_all()
295
- self.client.shutdown()
296
- if hasattr(super(), "cleanup"):
297
- super().cleanup()
298
-
299
- # def closeEvent(self, event):
300
- # self.cleanup()
301
- # super().closeEvent(event)
@@ -1,8 +1,23 @@
1
- class BECWidget:
2
- """Base class for all BEC widgets."""
1
+ from qtpy.QtWidgets import QWidget
2
+
3
+ from bec_widgets.utils.bec_connector import BECConnector, ConnectionConfig
4
+
5
+
6
+ class BECWidget(BECConnector):
7
+ """Mixin class for all BEC widgets, to handle cleanup"""
8
+
9
+ def __init__(self, client=None, config: ConnectionConfig = None, gui_id: str = None):
10
+ if not isinstance(self, QWidget):
11
+ raise RuntimeError(f"{repr(self)} is not a subclass of QWidget")
12
+ super().__init__(client, config, gui_id)
13
+
14
+ def cleanup(self):
15
+ """Cleanup the widget."""
16
+ pass
3
17
 
4
18
  def closeEvent(self, event):
5
- if hasattr(self, "cleanup"):
19
+ self.rpc_register.remove_rpc(self)
20
+ try:
6
21
  self.cleanup()
7
- if hasattr(super(), "closeEvent"):
22
+ finally:
8
23
  super().closeEvent(event)
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
- from bec_widgets.utils import BECConnector, ConnectionConfig
3
+ from bec_widgets.utils import ConnectionConfig
4
+ from bec_widgets.utils.bec_widget import BECWidget
4
5
 
5
6
 
6
7
  class DeviceInputConfig(ConnectionConfig):
@@ -9,7 +10,7 @@ class DeviceInputConfig(ConnectionConfig):
9
10
  arg_name: str | None = None
10
11
 
11
12
 
12
- class DeviceInputBase(BECConnector):
13
+ class DeviceInputBase(BECWidget):
13
14
  """
14
15
  Mixin class for device input widgets. This class provides methods to get the device list and device object based
15
16
  on the current text of the widget.
@@ -120,6 +121,3 @@ class DeviceInputBase(BECConnector):
120
121
  """
121
122
  if device not in self.get_device_list(self.config.device_filter):
122
123
  raise ValueError(f"Device {device} is not valid.")
123
-
124
- def cleanup(self):
125
- super().cleanup()
@@ -2,10 +2,11 @@ from bec_lib.endpoints import MessageEndpoints
2
2
  from qtpy.QtCore import Qt, Slot
3
3
  from qtpy.QtWidgets import QHeaderView, QTableWidget, QTableWidgetItem, QWidget
4
4
 
5
- from bec_widgets.utils.bec_connector import BECConnector, ConnectionConfig
5
+ from bec_widgets.utils.bec_connector import ConnectionConfig
6
+ from bec_widgets.utils.bec_widget import BECWidget
6
7
 
7
8
 
8
- class BECQueue(BECConnector, QTableWidget):
9
+ class BECQueue(BECWidget, QTableWidget):
9
10
  """
10
11
  Widget to display the BEC queue.
11
12
  """
@@ -13,7 +13,7 @@ from bec_lib.utils.import_utils import lazy_import_from
13
13
  from qtpy.QtCore import QObject, QTimer, Signal, Slot
14
14
  from qtpy.QtWidgets import QHBoxLayout, QTreeWidget, QTreeWidgetItem, QWidget
15
15
 
16
- from bec_widgets.utils.bec_connector import BECConnector
16
+ from bec_widgets.utils.bec_widget import BECWidget
17
17
  from bec_widgets.utils.colors import apply_theme
18
18
  from bec_widgets.widgets.bec_status_box.status_item import StatusItem
19
19
 
@@ -57,7 +57,7 @@ class BECServiceStatusMixin(QObject):
57
57
  self.services_update.emit(self.client._services_info, self.client._services_metric)
58
58
 
59
59
 
60
- class BECStatusBox(BECConnector, QWidget):
60
+ class BECStatusBox(BECWidget, QWidget):
61
61
  """An autonomous widget to display the status of BEC services.
62
62
 
63
63
  Args:
@@ -290,15 +290,6 @@ class BECStatusBox(BECConnector, QWidget):
290
290
  if objects["item"] == item:
291
291
  objects["widget"].show_popup()
292
292
 
293
- def closeEvent(self, event):
294
- """Upon closing the widget, clean up the BECStatusBox and the QWidget.
295
-
296
- Args:
297
- event: The close event.
298
- """
299
- super().cleanup()
300
- super().closeEvent(event)
301
-
302
293
 
303
294
  def main():
304
295
  """Main method to run the BECStatusBox widget."""
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import pyqtgraph as pg
2
4
  from qtpy.QtCore import Property, Signal, Slot
3
5
  from qtpy.QtGui import QColor, QFontMetrics, QImage
@@ -46,7 +48,7 @@ class ColormapSelector(QWidget):
46
48
  colormap_changed_signal = Signal(str)
47
49
 
48
50
  def __init__(self, parent=None, default_colormaps=None):
49
- super().__init__(parent)
51
+ super().__init__(parent=parent)
50
52
  self._colormaps = []
51
53
  self.initUI(default_colormaps)
52
54
 
@@ -0,0 +1 @@
1
+ {'files': ['colormap_selector.py']}