bec-widgets 0.51.0__py3-none-any.whl → 0.52.1__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 (48) hide show
  1. bec_widgets/cli/__init__.py +1 -1
  2. bec_widgets/cli/bec_widgets_icon.png +0 -0
  3. bec_widgets/cli/client.py +328 -5
  4. bec_widgets/cli/client_utils.py +17 -5
  5. bec_widgets/cli/generate_cli.py +7 -3
  6. bec_widgets/cli/rpc_register.py +4 -0
  7. bec_widgets/cli/rpc_wigdet_handler.py +27 -0
  8. bec_widgets/cli/server.py +34 -9
  9. bec_widgets/examples/jupyter_console/jupyter_console_window.py +54 -2
  10. bec_widgets/examples/jupyter_console/jupyter_console_window.ui +26 -2
  11. bec_widgets/examples/jupyter_console/terminal_icon.png +0 -0
  12. bec_widgets/utils/__init__.py +1 -0
  13. bec_widgets/utils/bec_connector.py +18 -3
  14. bec_widgets/utils/bec_table.py +1 -0
  15. bec_widgets/utils/container_utils.py +3 -0
  16. bec_widgets/utils/crosshair.py +1 -0
  17. bec_widgets/utils/entry_validator.py +2 -0
  18. bec_widgets/utils/layout_manager.py +121 -0
  19. bec_widgets/utils/widget_io.py +5 -0
  20. bec_widgets/utils/yaml_dialog.py +2 -0
  21. bec_widgets/validation/monitor_config_validator.py +2 -1
  22. bec_widgets/widgets/__init__.py +1 -0
  23. bec_widgets/widgets/dock/__init__.py +2 -0
  24. bec_widgets/widgets/dock/dock.py +269 -0
  25. bec_widgets/widgets/dock/dock_area.py +225 -0
  26. bec_widgets/widgets/figure/figure.py +45 -16
  27. bec_widgets/widgets/plots/__init__.py +1 -1
  28. bec_widgets/widgets/plots/image.py +46 -9
  29. bec_widgets/widgets/plots/motor_map.py +17 -3
  30. bec_widgets/widgets/plots/plot_base.py +14 -3
  31. bec_widgets/widgets/plots/waveform.py +37 -10
  32. bec_widgets/widgets/scan_control/scan_control.py +10 -2
  33. bec_widgets/widgets/toolbar/toolbar.py +1 -0
  34. {bec_widgets-0.51.0.dist-info → bec_widgets-0.52.1.dist-info}/METADATA +1 -1
  35. {bec_widgets-0.51.0.dist-info → bec_widgets-0.52.1.dist-info}/RECORD +46 -40
  36. tests/end-2-end/conftest.py +19 -4
  37. tests/end-2-end/test_bec_dock_rpc_e2e.py +145 -0
  38. tests/end-2-end/test_bec_figure_rpc_e2e.py +17 -17
  39. tests/end-2-end/test_rpc_register_e2e.py +3 -3
  40. tests/unit_tests/test_bec_dock.py +114 -0
  41. tests/unit_tests/test_bec_figure.py +20 -22
  42. tests/unit_tests/test_generate_cli_client.py +1 -1
  43. tests/unit_tests/test_waveform1d.py +1 -1
  44. bec_widgets/simulations/__init__.py +0 -0
  45. bec_widgets/utils/ctrl_c.py +0 -39
  46. {bec_widgets-0.51.0.dist-info → bec_widgets-0.52.1.dist-info}/LICENSE +0 -0
  47. {bec_widgets-0.51.0.dist-info → bec_widgets-0.52.1.dist-info}/WHEEL +0 -0
  48. {bec_widgets-0.51.0.dist-info → bec_widgets-0.52.1.dist-info}/top_level.txt +0 -0
@@ -114,6 +114,7 @@ class WidgetIO:
114
114
  def get_value(widget, ignore_errors=False):
115
115
  """
116
116
  Retrieve value from the widget instance.
117
+
117
118
  Args:
118
119
  widget: Widget instance.
119
120
  ignore_errors(bool, optional): Whether to ignore if no handler is found.
@@ -129,6 +130,7 @@ class WidgetIO:
129
130
  def set_value(widget, value, ignore_errors=False):
130
131
  """
131
132
  Set a value on the widget instance.
133
+
132
134
  Args:
133
135
  widget: Widget instance.
134
136
  value: Value to set.
@@ -155,6 +157,7 @@ class WidgetHierarchy:
155
157
  ) -> None:
156
158
  """
157
159
  Print the widget hierarchy to the console.
160
+
158
161
  Args:
159
162
  widget: Widget to print the hierarchy of
160
163
  indent(int, optional): Level of indentation.
@@ -196,6 +199,7 @@ class WidgetHierarchy:
196
199
  ) -> dict:
197
200
  """
198
201
  Export the widget hierarchy to a dictionary.
202
+
199
203
  Args:
200
204
  widget: Widget to print the hierarchy of.
201
205
  config(dict,optional): Dictionary to export the hierarchy to.
@@ -245,6 +249,7 @@ class WidgetHierarchy:
245
249
  def import_config_from_dict(widget, config: dict, set_values: bool = False) -> None:
246
250
  """
247
251
  Import the widget hierarchy from a dictionary.
252
+
248
253
  Args:
249
254
  widget: Widget to import the hierarchy to.
250
255
  config:
@@ -9,6 +9,7 @@ from qtpy.QtWidgets import QFileDialog
9
9
  def load_yaml(instance) -> Union[dict, None]:
10
10
  """
11
11
  Load YAML file from disk.
12
+
12
13
  Args:
13
14
  instance: Instance of the calling widget.
14
15
 
@@ -40,6 +41,7 @@ def load_yaml(instance) -> Union[dict, None]:
40
41
  def save_yaml(instance, config: dict) -> None:
41
42
  """
42
43
  Save YAML file to disk.
44
+
43
45
  Args:
44
46
  instance: Instance of the calling widget.
45
47
  config: Configuration data to be saved.
@@ -8,7 +8,7 @@ class Signal(BaseModel):
8
8
  """
9
9
  Represents a signal in a plot configuration.
10
10
 
11
- Attributes:
11
+ Args:
12
12
  name (str): The name of the signal.
13
13
  entry (Optional[str]): The entry point of the signal, optional.
14
14
  """
@@ -21,6 +21,7 @@ class Signal(BaseModel):
21
21
  def validate_fields(cls, values):
22
22
  """Validate the fields of the model.
23
23
  First validate the 'name' field, then validate the 'entry' field.
24
+
24
25
  Args:
25
26
  values (dict): The values to be validated."""
26
27
  devices = MonitorConfigValidator.devices
@@ -1,3 +1,4 @@
1
+ from .dock import BECDock, BECDockArea
1
2
  from .figure import BECFigure, FigureConfig
2
3
  from .monitor import BECMonitor
3
4
  from .motor_control import (
@@ -0,0 +1,2 @@
1
+ from .dock import BECDock
2
+ from .dock_area import BECDockArea
@@ -0,0 +1,269 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Literal, Optional
4
+
5
+ from pydantic import Field
6
+ from pyqtgraph.dockarea import Dock
7
+
8
+ from bec_widgets.cli.rpc_wigdet_handler import RPCWidgetHandler
9
+ from bec_widgets.utils import BECConnector, ConnectionConfig, GridLayoutManager
10
+
11
+ if TYPE_CHECKING:
12
+ from qtpy.QtWidgets import QWidget
13
+
14
+ from bec_widgets.widgets import BECDockArea
15
+
16
+
17
+ class DockConfig(ConnectionConfig):
18
+ widgets: dict[str, ConnectionConfig] = Field({}, description="The widgets in the dock.")
19
+ position: Literal["bottom", "top", "left", "right", "above", "below"] = Field(
20
+ "bottom", description="The position of the dock."
21
+ )
22
+ parent_dock_area: Optional[str] = Field(
23
+ None, description="The GUI ID of parent dock area of the dock."
24
+ )
25
+
26
+
27
+ class BECDock(BECConnector, Dock):
28
+ USER_ACCESS = [
29
+ "rpc_id",
30
+ "widget_list",
31
+ "show_title_bar",
32
+ "hide_title_bar",
33
+ "get_widgets_positions",
34
+ "set_title",
35
+ "add_widget_bec",
36
+ "list_eligible_widgets",
37
+ "move_widget",
38
+ "remove_widget",
39
+ "remove",
40
+ "attach",
41
+ "detach",
42
+ ]
43
+
44
+ def __init__(
45
+ self,
46
+ parent: QWidget | None = None,
47
+ parent_dock_area: BECDockArea | None = None,
48
+ config: DockConfig | None = None,
49
+ name: str | None = None,
50
+ client=None,
51
+ gui_id: str | None = None,
52
+ **kwargs,
53
+ ) -> None:
54
+ if config is None:
55
+ config = DockConfig(
56
+ widget_class=self.__class__.__name__, parent_dock_area=parent_dock_area.gui_id
57
+ )
58
+ else:
59
+ if isinstance(config, dict):
60
+ config = DockConfig(**config)
61
+ self.config = config
62
+ super().__init__(client=client, config=config, gui_id=gui_id)
63
+ Dock.__init__(self, name=name, **kwargs)
64
+
65
+ self.parent_dock_area = parent_dock_area
66
+
67
+ # Layout Manager
68
+ self.layout_manager = GridLayoutManager(self.layout)
69
+
70
+ def dropEvent(self, event):
71
+ source = event.source()
72
+ old_area = source.area
73
+ self.setOrientation("horizontal", force=True)
74
+ super().dropEvent(event)
75
+ if old_area in self.parent_dock_area.tempAreas and old_area != self.parent_dock_area:
76
+ self.parent_dock_area.removeTempArea(old_area)
77
+
78
+ def float(self):
79
+ """
80
+ Float the dock.
81
+ Overwrites the default pyqtgraph dock float.
82
+ """
83
+
84
+ # need to check if the dock is temporary and if it is the only dock in the area
85
+ # fixes bug in pyqtgraph detaching
86
+ if self.area.temporary == True and len(self.area.docks) <= 1:
87
+ return
88
+ elif self.area.temporary == True and len(self.area.docks) > 1:
89
+ self.area.docks.pop(self.name(), None)
90
+ super().float()
91
+ else:
92
+ super().float()
93
+
94
+ @property
95
+ def widget_list(self) -> list:
96
+ """
97
+ Get the widgets in the dock.
98
+
99
+ Returns:
100
+ widgets(list): The widgets in the dock.
101
+ """
102
+ return self.widgets
103
+
104
+ @widget_list.setter
105
+ def widget_list(self, value: list):
106
+ self.widgets = value
107
+
108
+ def hide_title_bar(self):
109
+ """
110
+ Hide the title bar of the dock.
111
+ """
112
+ # self.hideTitleBar() #TODO pyqtgraph looks bugged ATM, doing my implementation
113
+ self.label.hide()
114
+ self.labelHidden = True
115
+
116
+ def show_title_bar(self):
117
+ """
118
+ Hide the title bar of the dock.
119
+ """
120
+ # self.showTitleBar() #TODO pyqtgraph looks bugged ATM, doing my implementation
121
+ self.label.show()
122
+ self.labelHidden = False
123
+
124
+ def set_title(self, title: str):
125
+ """
126
+ Set the title of the dock.
127
+
128
+ Args:
129
+ title(str): The title of the dock.
130
+ """
131
+ self.parent_dock_area.docks[title] = self.parent_dock_area.docks.pop(self.name())
132
+ self.setTitle(title)
133
+
134
+ def get_widgets_positions(self) -> dict:
135
+ """
136
+ Get the positions of the widgets in the dock.
137
+
138
+ Returns:
139
+ dict: The positions of the widgets in the dock as dict -> {(row, col, rowspan, colspan):widget}
140
+ """
141
+ return self.layout_manager.get_widgets_positions()
142
+
143
+ def list_eligible_widgets(
144
+ self,
145
+ ) -> list: # TODO can be moved to some util mixin like container class for rpc widgets
146
+ """
147
+ List all widgets that can be added to the dock.
148
+
149
+ Returns:
150
+ list: The list of eligible widgets.
151
+ """
152
+ return list(RPCWidgetHandler.widget_classes.keys())
153
+
154
+ def add_widget_bec(
155
+ self,
156
+ widget_type: str,
157
+ row=None,
158
+ col=0,
159
+ rowspan=1,
160
+ colspan=1,
161
+ shift: Literal["down", "up", "left", "right"] = "down",
162
+ ):
163
+ """
164
+ Add a widget to the dock.
165
+
166
+ Args:
167
+ widget_type(str): The widget to add. Only BEC RPC widgets from RPCWidgetHandler are allowed.
168
+ row(int): The row to add the widget to. If None, the widget will be added to the next available row.
169
+ col(int): The column to add the widget to.
170
+ rowspan(int): The number of rows the widget should span.
171
+ colspan(int): The number of columns the widget should span.
172
+ shift(Literal["down", "up", "left", "right"]): The direction to shift the widgets if the position is occupied.
173
+ """
174
+ if row is None:
175
+ row = self.layout.rowCount()
176
+
177
+ if self.layout_manager.is_position_occupied(row, col):
178
+ self.layout_manager.shift_widgets(shift, start_row=row)
179
+
180
+ widget = RPCWidgetHandler.create_widget(widget_type)
181
+ self.addWidget(widget, row=row, col=col, rowspan=rowspan, colspan=colspan)
182
+
183
+ return widget
184
+
185
+ def add_widget(
186
+ self,
187
+ widget: QWidget,
188
+ row=None,
189
+ col=0,
190
+ rowspan=1,
191
+ colspan=1,
192
+ shift: Literal["down", "up", "left", "right"] = "down",
193
+ ):
194
+ """
195
+ Add a widget to the dock.
196
+
197
+ Args:
198
+ widget(QWidget): The widget to add.
199
+ row(int): The row to add the widget to. If None, the widget will be added to the next available row.
200
+ col(int): The column to add the widget to.
201
+ rowspan(int): The number of rows the widget should span.
202
+ colspan(int): The number of columns the widget should span.
203
+ shift(Literal["down", "up", "left", "right"]): The direction to shift the widgets if the position is occupied.
204
+ """
205
+ if row is None:
206
+ row = self.layout.rowCount()
207
+
208
+ if self.layout_manager.is_position_occupied(row, col):
209
+ self.layout_manager.shift_widgets(shift, start_row=row)
210
+
211
+ self.addWidget(widget, row=row, col=col, rowspan=rowspan, colspan=colspan)
212
+
213
+ def move_widget(self, widget: QWidget, new_row: int, new_col: int):
214
+ """
215
+ Move a widget to a new position in the layout.
216
+
217
+ Args:
218
+ widget(QWidget): The widget to move.
219
+ new_row(int): The new row to move the widget to.
220
+ new_col(int): The new column to move the widget to.
221
+ """
222
+ self.layout_manager.move_widget(widget, new_row, new_col)
223
+
224
+ def attach(self):
225
+ """
226
+ Attach the dock to the parent dock area.
227
+ """
228
+ self.parent_dock_area.removeTempArea(self.area)
229
+
230
+ def detach(self):
231
+ """
232
+ Detach the dock from the parent dock area.
233
+ """
234
+ self.float()
235
+
236
+ def remove_widget(self, widget_rpc_id: str):
237
+ """
238
+ Remove a widget from the dock.
239
+
240
+ Args:
241
+ widget_rpc_id(str): The ID of the widget to remove.
242
+ """
243
+ widget = self.rpc_register.get_rpc_by_id(widget_rpc_id)
244
+ self.layout.removeWidget(widget)
245
+ widget.close()
246
+
247
+ def remove(self):
248
+ """
249
+ Remove the dock from the parent dock area.
250
+ """
251
+ # self.cleanup()
252
+ self.parent_dock_area.remove_dock(self.name())
253
+
254
+ def cleanup(self):
255
+ """
256
+ Clean up the dock, including all its widgets.
257
+ """
258
+ for widget in self.widgets:
259
+ if hasattr(widget, "cleanup"):
260
+ widget.cleanup()
261
+ super().cleanup()
262
+
263
+ def close(self):
264
+ """
265
+ Close the dock area and cleanup.
266
+ Has to be implemented to overwrite pyqtgraph event accept in Container close.
267
+ """
268
+ self.cleanup()
269
+ super().close()
@@ -0,0 +1,225 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal, Optional
4
+ from weakref import WeakValueDictionary
5
+
6
+ from pydantic import Field
7
+ from pyqtgraph.dockarea.DockArea import DockArea
8
+ from qtpy.QtCore import Qt
9
+ from qtpy.QtGui import QPainter, QPaintEvent
10
+ from qtpy.QtWidgets import QWidget
11
+
12
+ from bec_widgets.utils import BECConnector, ConnectionConfig, WidgetContainerUtils
13
+
14
+ from .dock import BECDock, DockConfig
15
+
16
+
17
+ class DockAreaConfig(ConnectionConfig):
18
+ docks: dict[str, DockConfig] = Field({}, description="The docks in the dock area.")
19
+
20
+
21
+ class BECDockArea(BECConnector, DockArea):
22
+ USER_ACCESS = [
23
+ "panels",
24
+ "save_state",
25
+ "remove_dock",
26
+ "restore_state",
27
+ "add_dock",
28
+ "clear_all",
29
+ "detach_dock",
30
+ "attach_all",
31
+ "get_all_rpc",
32
+ ]
33
+
34
+ def __init__(
35
+ self,
36
+ parent: QWidget | None = None,
37
+ config: DockAreaConfig | None = None,
38
+ client=None,
39
+ gui_id: str = None,
40
+ ) -> None:
41
+ if config is None:
42
+ config = DockAreaConfig(widget_class=self.__class__.__name__)
43
+ else:
44
+ if isinstance(config, dict):
45
+ config = DockAreaConfig(**config)
46
+ self.config = config
47
+ super().__init__(client=client, config=config, gui_id=gui_id)
48
+ DockArea.__init__(self, parent=parent)
49
+
50
+ self._instructions_visible = True
51
+
52
+ def paintEvent(self, event: QPaintEvent):
53
+ super().paintEvent(event)
54
+ if self._instructions_visible:
55
+ painter = QPainter(self)
56
+ painter.drawText(self.rect(), Qt.AlignCenter, "Add docks using 'add_dock' method")
57
+
58
+ @property
59
+ def panels(self) -> dict:
60
+ """
61
+ Get the docks in the dock area.
62
+ Returns:
63
+ dock_dict(dict): The docks in the dock area.
64
+ """
65
+ return dict(self.docks)
66
+
67
+ @panels.setter
68
+ def panels(self, value: dict):
69
+
70
+ self.docks = WeakValueDictionary(value)
71
+
72
+ def restore_state(
73
+ self,
74
+ state: dict = None,
75
+ missing: Literal["ignore", "error"] = "ignore",
76
+ extra="bottom",
77
+ ):
78
+ """
79
+ Restore the state of the dock area. If no state is provided, the last state is restored.
80
+
81
+ Args:
82
+ state(dict): The state to restore.
83
+ missing(Literal['ignore','error']): What to do if a dock is missing.
84
+ extra(str): Extra docks that are in the dockarea but that are not mentioned in state will be added to the bottom of the dockarea, unless otherwise specified by the extra argument.
85
+ """
86
+ if state is None:
87
+ state = self._last_state
88
+ self.restoreState(state, missing=missing, extra=extra)
89
+
90
+ def save_state(self) -> dict:
91
+ """
92
+ Save the state of the dock area.
93
+
94
+ Returns:
95
+ dict: The state of the dock area.
96
+ """
97
+ self._last_state = self.saveState()
98
+ return self._last_state
99
+
100
+ def remove_dock(self, name: str):
101
+ """
102
+ Remove a dock by name and ensure it is properly closed and cleaned up.
103
+
104
+ Args:
105
+ name(str): The name of the dock to remove.
106
+ """
107
+ dock = self.docks.pop(name, None)
108
+ if dock:
109
+ dock.close()
110
+ if len(self.docks) <= 1:
111
+ for dock in self.docks.values():
112
+ dock.hide_title_bar()
113
+
114
+ else:
115
+ raise ValueError(f"Dock with name {name} does not exist.")
116
+
117
+ def add_dock(
118
+ self,
119
+ name: str = None,
120
+ position: Literal["bottom", "top", "left", "right", "above", "below"] = None,
121
+ relative_to: BECDock | None = None,
122
+ closable: bool = False,
123
+ prefix: str = "dock",
124
+ widget: str | QWidget | None = None,
125
+ row: int = None,
126
+ col: int = 0,
127
+ rowspan: int = 1,
128
+ colspan: int = 1,
129
+ ) -> BECDock:
130
+ """
131
+ Add a dock to the dock area. Dock has QGridLayout as layout manager by default.
132
+
133
+ Args:
134
+ name(str): The name of the dock to be displayed and for further references. Has to be unique.
135
+ position(Literal["bottom", "top", "left", "right", "above", "below"]): The position of the dock.
136
+ relative_to(BECDock): The dock to which the new dock should be added relative to.
137
+ closable(bool): Whether the dock is closable.
138
+ prefix(str): The prefix for the dock name if no name is provided.
139
+ widget(str|QWidget|None): The widget to be added to the dock. While using RPC, only BEC RPC widgets from RPCWidgetHandler are allowed.
140
+ row(int): The row of the added widget.
141
+ col(int): The column of the added widget.
142
+ rowspan(int): The rowspan of the added widget.
143
+ colspan(int): The colspan of the added widget.
144
+
145
+ Returns:
146
+ BECDock: The created dock.
147
+ """
148
+ if name is None:
149
+ name = WidgetContainerUtils.generate_unique_widget_id(
150
+ container=self.docks, prefix=prefix
151
+ )
152
+
153
+ if name in set(self.docks.keys()):
154
+ raise ValueError(f"Dock with name {name} already exists.")
155
+
156
+ if position is None:
157
+ position = "bottom"
158
+
159
+ dock = BECDock(name=name, parent_dock_area=self, closable=closable)
160
+ dock.config.position = position
161
+ self.config.docks[name] = dock.config
162
+
163
+ self.addDock(dock=dock, position=position, relativeTo=relative_to)
164
+
165
+ if len(self.docks) <= 1:
166
+ dock.hide_title_bar()
167
+ elif len(self.docks) > 1:
168
+ for dock in self.docks.values():
169
+ dock.show_title_bar()
170
+
171
+ if widget is not None and isinstance(widget, str):
172
+ dock.add_widget_bec(
173
+ widget_type=widget, row=row, col=col, rowspan=rowspan, colspan=colspan
174
+ )
175
+ elif widget is not None and isinstance(widget, QWidget):
176
+ dock.addWidget(widget, row=row, col=col, rowspan=rowspan, colspan=colspan)
177
+ if self._instructions_visible:
178
+ self._instructions_visible = False
179
+ self.update()
180
+ return dock
181
+
182
+ def detach_dock(self, dock_name: str) -> BECDock:
183
+ """
184
+ Undock a dock from the dock area.
185
+
186
+ Args:
187
+ dock_name(str): The dock to undock.
188
+
189
+ Returns:
190
+ BECDock: The undocked dock.
191
+ """
192
+ dock = self.docks[dock_name]
193
+ self.floatDock(dock)
194
+ return dock
195
+
196
+ def attach_all(self):
197
+ """
198
+ Return all floating docks to the dock area.
199
+ """
200
+ while self.tempAreas:
201
+ for temp_area in self.tempAreas:
202
+ self.removeTempArea(temp_area)
203
+
204
+ def clear_all(self):
205
+ """
206
+ Close all docks and remove all temp areas.
207
+ """
208
+ self.attach_all()
209
+ for dock in dict(self.docks).values():
210
+ dock.remove()
211
+
212
+ def cleanup(self):
213
+ """
214
+ Cleanup the dock area.
215
+ """
216
+ self.clear_all()
217
+ super().cleanup()
218
+
219
+ def close(self):
220
+ """
221
+ Close the dock area and cleanup.
222
+ Has to be implemented to overwrite pyqtgraph event accept in Container close.
223
+ """
224
+ self.cleanup()
225
+ super().close()