bec-widgets 0.51.0__py3-none-any.whl → 0.52.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.
- bec_widgets/cli/__init__.py +1 -1
- bec_widgets/cli/bec_widgets_icon.png +0 -0
- bec_widgets/cli/client.py +213 -5
- bec_widgets/cli/client_utils.py +16 -5
- bec_widgets/cli/generate_cli.py +6 -3
- bec_widgets/cli/rpc_wigdet_handler.py +26 -0
- bec_widgets/cli/server.py +34 -9
- bec_widgets/examples/jupyter_console/jupyter_console_window.py +54 -2
- bec_widgets/examples/jupyter_console/jupyter_console_window.ui +26 -2
- bec_widgets/examples/jupyter_console/terminal_icon.png +0 -0
- bec_widgets/utils/__init__.py +1 -0
- bec_widgets/utils/bec_connector.py +11 -3
- bec_widgets/utils/layout_manager.py +117 -0
- bec_widgets/widgets/__init__.py +1 -0
- bec_widgets/widgets/dock/__init__.py +2 -0
- bec_widgets/widgets/dock/dock.py +269 -0
- bec_widgets/widgets/dock/dock_area.py +220 -0
- bec_widgets/widgets/figure/figure.py +33 -16
- bec_widgets/widgets/plots/__init__.py +1 -1
- bec_widgets/widgets/plots/image.py +5 -9
- bec_widgets/widgets/plots/motor_map.py +3 -3
- bec_widgets/widgets/plots/plot_base.py +4 -3
- bec_widgets/widgets/plots/waveform.py +6 -10
- {bec_widgets-0.51.0.dist-info → bec_widgets-0.52.0.dist-info}/METADATA +1 -1
- {bec_widgets-0.51.0.dist-info → bec_widgets-0.52.0.dist-info}/RECORD +36 -28
- tests/end-2-end/conftest.py +19 -4
- tests/end-2-end/test_bec_dock_rpc_e2e.py +145 -0
- tests/end-2-end/test_bec_figure_rpc_e2e.py +17 -17
- tests/end-2-end/test_rpc_register_e2e.py +3 -3
- tests/unit_tests/test_bec_dock.py +114 -0
- tests/unit_tests/test_bec_figure.py +20 -22
- tests/unit_tests/test_generate_cli_client.py +1 -1
- tests/unit_tests/test_waveform1d.py +1 -1
- {bec_widgets-0.51.0.dist-info → bec_widgets-0.52.0.dist-info}/LICENSE +0 -0
- {bec_widgets-0.51.0.dist-info → bec_widgets-0.52.0.dist-info}/WHEEL +0 -0
- {bec_widgets-0.51.0.dist-info → bec_widgets-0.52.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,117 @@
|
|
1
|
+
from collections import OrderedDict
|
2
|
+
from typing import Literal
|
3
|
+
|
4
|
+
from qtpy.QtWidgets import QGridLayout, QWidget
|
5
|
+
|
6
|
+
|
7
|
+
class GridLayoutManager:
|
8
|
+
"""
|
9
|
+
GridLayoutManager class is used to manage widgets in a QGridLayout and extend its functionality.
|
10
|
+
|
11
|
+
The GridLayoutManager class provides methods to add, move, and check the position of widgets in a QGridLayout.
|
12
|
+
It also provides a method to get the positions of all widgets in the layout.
|
13
|
+
|
14
|
+
Args:
|
15
|
+
layout(QGridLayout): The layout to manage.
|
16
|
+
"""
|
17
|
+
|
18
|
+
def __init__(self, layout: QGridLayout):
|
19
|
+
self.layout = layout
|
20
|
+
|
21
|
+
def is_position_occupied(self, row: int, col: int) -> bool:
|
22
|
+
"""
|
23
|
+
Check if the position in the layout is occupied by a widget.
|
24
|
+
Args:
|
25
|
+
row(int): The row to check.
|
26
|
+
col(int): The column to check.
|
27
|
+
|
28
|
+
Returns:
|
29
|
+
bool: True if the position is occupied, False otherwise.
|
30
|
+
"""
|
31
|
+
for i in range(self.layout.count()):
|
32
|
+
widget_row, widget_col, _, _ = self.layout.getItemPosition(i)
|
33
|
+
if widget_row == row and widget_col == col:
|
34
|
+
return True
|
35
|
+
return False
|
36
|
+
|
37
|
+
def shift_widgets(
|
38
|
+
self,
|
39
|
+
direction: Literal["down", "up", "left", "right"] = "down",
|
40
|
+
start_row: int = 0,
|
41
|
+
start_col: int = 0,
|
42
|
+
):
|
43
|
+
"""
|
44
|
+
Shift widgets in the layout in the specified direction starting from the specified position.
|
45
|
+
Args:
|
46
|
+
direction(str): The direction to shift the widgets. Can be "down", "up", "left", or "right".
|
47
|
+
start_row(int): The row to start shifting from. Default is 0.
|
48
|
+
start_col(int): The column to start shifting from. Default is 0.
|
49
|
+
"""
|
50
|
+
for i in reversed(range(self.layout.count())):
|
51
|
+
widget_item = self.layout.itemAt(i)
|
52
|
+
widget = widget_item.widget()
|
53
|
+
row, col, rowspan, colspan = self.layout.getItemPosition(i)
|
54
|
+
if direction == "down" and row >= start_row:
|
55
|
+
self.layout.addWidget(widget, row + 1, col, rowspan, colspan)
|
56
|
+
elif direction == "up" and row > start_row:
|
57
|
+
self.layout.addWidget(widget, row - 1, col, rowspan, colspan)
|
58
|
+
elif direction == "right" and col >= start_col:
|
59
|
+
self.layout.addWidget(widget, row, col + 1, rowspan, colspan)
|
60
|
+
elif direction == "left" and col > start_col:
|
61
|
+
self.layout.addWidget(widget, row, col - 1, rowspan, colspan)
|
62
|
+
|
63
|
+
def move_widget(self, widget: QWidget, new_row: int, new_col: int):
|
64
|
+
"""
|
65
|
+
Move a widget to a new position in the layout.
|
66
|
+
Args:
|
67
|
+
widget(QWidget): The widget to move.
|
68
|
+
new_row(int): The new row to move the widget to.
|
69
|
+
new_col(int): The new column to move the widget to.
|
70
|
+
"""
|
71
|
+
self.layout.removeWidget(widget)
|
72
|
+
self.layout.addWidget(widget, new_row, new_col)
|
73
|
+
|
74
|
+
def add_widget(
|
75
|
+
self,
|
76
|
+
widget: QWidget,
|
77
|
+
row=None,
|
78
|
+
col=0,
|
79
|
+
rowspan=1,
|
80
|
+
colspan=1,
|
81
|
+
shift: Literal["down", "up", "left", "right"] = "down",
|
82
|
+
):
|
83
|
+
"""
|
84
|
+
Add a widget to the layout at the specified position.
|
85
|
+
Args:
|
86
|
+
widget(QWidget): The widget to add.
|
87
|
+
row(int): The row to add the widget to. If None, the widget will be added to the next available row.
|
88
|
+
col(int): The column to add the widget to. Default is 0.
|
89
|
+
rowspan(int): The number of rows the widget will span. Default is 1.
|
90
|
+
colspan(int): The number of columns the widget will span. Default is 1.
|
91
|
+
shift(str): The direction to shift the widgets if the position is occupied. Can be "down", "up", "left", or "right".
|
92
|
+
"""
|
93
|
+
if row is None:
|
94
|
+
row = self.layout.rowCount()
|
95
|
+
if self.is_position_occupied(row, col):
|
96
|
+
self.shift_widgets(shift, start_row=row)
|
97
|
+
self.layout.addWidget(widget, row, col, rowspan, colspan)
|
98
|
+
|
99
|
+
def get_widgets_positions(self) -> dict:
|
100
|
+
"""
|
101
|
+
Get the positions of all widgets in the layout.
|
102
|
+
Returns:
|
103
|
+
dict: A dictionary with the positions of the widgets in the layout.
|
104
|
+
|
105
|
+
"""
|
106
|
+
positions = []
|
107
|
+
for i in range(self.layout.count()):
|
108
|
+
widget_item = self.layout.itemAt(i)
|
109
|
+
widget = widget_item.widget()
|
110
|
+
if widget:
|
111
|
+
position = self.layout.getItemPosition(i)
|
112
|
+
positions.append((position, widget))
|
113
|
+
positions.sort(key=lambda x: (x[0][0], x[0][1], x[0][2], x[0][3]))
|
114
|
+
ordered_positions = OrderedDict()
|
115
|
+
for pos, widget in positions:
|
116
|
+
ordered_positions[pos] = widget
|
117
|
+
return ordered_positions
|
bec_widgets/widgets/__init__.py
CHANGED
@@ -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,220 @@
|
|
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
|
+
Args:
|
81
|
+
state(dict): The state to restore.
|
82
|
+
missing(Literal['ignore','error']): What to do if a dock is missing.
|
83
|
+
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.
|
84
|
+
"""
|
85
|
+
if state is None:
|
86
|
+
state = self._last_state
|
87
|
+
self.restoreState(state, missing=missing, extra=extra)
|
88
|
+
|
89
|
+
def save_state(self) -> dict:
|
90
|
+
"""
|
91
|
+
Save the state of the dock area.
|
92
|
+
Returns:
|
93
|
+
dict: The state of the dock area.
|
94
|
+
"""
|
95
|
+
self._last_state = self.saveState()
|
96
|
+
return self._last_state
|
97
|
+
|
98
|
+
def remove_dock(self, name: str):
|
99
|
+
"""
|
100
|
+
Remove a dock by name and ensure it is properly closed and cleaned up.
|
101
|
+
Args:
|
102
|
+
name(str): The name of the dock to remove.
|
103
|
+
"""
|
104
|
+
dock = self.docks.pop(name, None)
|
105
|
+
if dock:
|
106
|
+
dock.close()
|
107
|
+
if len(self.docks) <= 1:
|
108
|
+
for dock in self.docks.values():
|
109
|
+
dock.hide_title_bar()
|
110
|
+
|
111
|
+
else:
|
112
|
+
raise ValueError(f"Dock with name {name} does not exist.")
|
113
|
+
|
114
|
+
def add_dock(
|
115
|
+
self,
|
116
|
+
name: str = None,
|
117
|
+
position: Literal["bottom", "top", "left", "right", "above", "below"] = None,
|
118
|
+
relative_to: BECDock | None = None,
|
119
|
+
closable: bool = False,
|
120
|
+
prefix: str = "dock",
|
121
|
+
widget: str | QWidget | None = None,
|
122
|
+
row: int = None,
|
123
|
+
col: int = 0,
|
124
|
+
rowspan: int = 1,
|
125
|
+
colspan: int = 1,
|
126
|
+
) -> BECDock:
|
127
|
+
"""
|
128
|
+
Add a dock to the dock area. Dock has QGridLayout as layout manager by default.
|
129
|
+
|
130
|
+
Args:
|
131
|
+
name(str): The name of the dock to be displayed and for further references. Has to be unique.
|
132
|
+
position(Literal["bottom", "top", "left", "right", "above", "below"]): The position of the dock.
|
133
|
+
relative_to(BECDock): The dock to which the new dock should be added relative to.
|
134
|
+
closable(bool): Whether the dock is closable.
|
135
|
+
prefix(str): The prefix for the dock name if no name is provided.
|
136
|
+
widget(str|QWidget|None): The widget to be added to the dock. While using RPC, only BEC RPC widgets from RPCWidgetHandler are allowed.
|
137
|
+
row(int): The row of the added widget.
|
138
|
+
col(int): The column of the added widget.
|
139
|
+
rowspan(int): The rowspan of the added widget.
|
140
|
+
colspan(int): The colspan of the added widget.
|
141
|
+
Returns:
|
142
|
+
BECDock: The created dock.
|
143
|
+
"""
|
144
|
+
if name is None:
|
145
|
+
name = WidgetContainerUtils.generate_unique_widget_id(
|
146
|
+
container=self.docks, prefix=prefix
|
147
|
+
)
|
148
|
+
|
149
|
+
if name in set(self.docks.keys()):
|
150
|
+
raise ValueError(f"Dock with name {name} already exists.")
|
151
|
+
|
152
|
+
if position is None:
|
153
|
+
position = "bottom"
|
154
|
+
|
155
|
+
dock = BECDock(name=name, parent_dock_area=self, closable=closable)
|
156
|
+
dock.config.position = position
|
157
|
+
self.config.docks[name] = dock.config
|
158
|
+
|
159
|
+
self.addDock(dock=dock, position=position, relativeTo=relative_to)
|
160
|
+
|
161
|
+
if len(self.docks) <= 1:
|
162
|
+
dock.hide_title_bar()
|
163
|
+
elif len(self.docks) > 1:
|
164
|
+
for dock in self.docks.values():
|
165
|
+
dock.show_title_bar()
|
166
|
+
|
167
|
+
if widget is not None and isinstance(widget, str):
|
168
|
+
dock.add_widget_bec(
|
169
|
+
widget_type=widget, row=row, col=col, rowspan=rowspan, colspan=colspan
|
170
|
+
)
|
171
|
+
elif widget is not None and isinstance(widget, QWidget):
|
172
|
+
dock.addWidget(widget, row=row, col=col, rowspan=rowspan, colspan=colspan)
|
173
|
+
if self._instructions_visible:
|
174
|
+
self._instructions_visible = False
|
175
|
+
self.update()
|
176
|
+
return dock
|
177
|
+
|
178
|
+
def detach_dock(self, dock_name: str) -> BECDock:
|
179
|
+
"""
|
180
|
+
Undock a dock from the dock area.
|
181
|
+
Args:
|
182
|
+
dock_name(str): The dock to undock.
|
183
|
+
|
184
|
+
Returns:
|
185
|
+
BECDock: The undocked dock.
|
186
|
+
"""
|
187
|
+
dock = self.docks[dock_name]
|
188
|
+
self.floatDock(dock)
|
189
|
+
return dock
|
190
|
+
|
191
|
+
def attach_all(self):
|
192
|
+
"""
|
193
|
+
Return all floating docks to the dock area.
|
194
|
+
"""
|
195
|
+
while self.tempAreas:
|
196
|
+
for temp_area in self.tempAreas:
|
197
|
+
self.removeTempArea(temp_area)
|
198
|
+
|
199
|
+
def clear_all(self):
|
200
|
+
"""
|
201
|
+
Close all docks and remove all temp areas.
|
202
|
+
"""
|
203
|
+
self.attach_all()
|
204
|
+
for dock in dict(self.docks).values():
|
205
|
+
dock.remove()
|
206
|
+
|
207
|
+
def cleanup(self):
|
208
|
+
"""
|
209
|
+
Cleanup the dock area.
|
210
|
+
"""
|
211
|
+
self.clear_all()
|
212
|
+
super().cleanup()
|
213
|
+
|
214
|
+
def close(self):
|
215
|
+
"""
|
216
|
+
Close the dock area and cleanup.
|
217
|
+
Has to be implemented to overwrite pyqtgraph event accept in Container close.
|
218
|
+
"""
|
219
|
+
self.cleanup()
|
220
|
+
super().close()
|