bec-widgets 1.9.0__py3-none-any.whl → 1.10.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.
CHANGELOG.md CHANGED
@@ -1,6 +1,22 @@
1
1
  # CHANGELOG
2
2
 
3
3
 
4
+ ## v1.10.0 (2024-12-10)
5
+
6
+ ### Features
7
+
8
+ - **layout_manager**: Grid layout manager widget
9
+ ([`17a63e3`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/17a63e3b639ecf6b41c379717d81339b04ef10f8))
10
+
11
+
12
+ ## v1.9.1 (2024-12-10)
13
+
14
+ ### Bug Fixes
15
+
16
+ - **designer**: General way to find python lib on linux
17
+ ([`6563abf`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6563abfddc9fc9baba6769022d6925545decdba9))
18
+
19
+
4
20
  ## v1.9.0 (2024-12-10)
5
21
 
6
22
  ### Features
@@ -185,9 +201,6 @@ Depending on the test, auto-updates are enabled or not.
185
201
  - **crosshair**: Label of coordinates of TextItem is updated according to the current theme of qapp
186
202
  ([`4f31ea6`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4f31ea655cf6190e141e6a2720a2d6da517a2b5b))
187
203
 
188
- - **crosshair**: Log is separately scaled for backend logic and for signal emit
189
- ([`b2eb71a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b2eb71aae0b6a7c82158f2d150ae1e31411cfdeb))
190
-
191
204
  ### Features
192
205
 
193
206
  - **crosshair**: Textitem to display crosshair coordinates
@@ -197,16 +210,3 @@ Depending on the test, auto-updates are enabled or not.
197
210
 
198
211
  - **crosshair**: Tests extended
199
212
  ([`64df805`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/64df805a9ed92bb97e580ac3bc0a1bbd2b1cb81e))
200
-
201
-
202
- ## v1.3.3 (2024-11-07)
203
-
204
- ### Bug Fixes
205
-
206
- - **scan_control**: Devicelineedit kwargs readings changed to get name of the positioner
207
- ([`5fabd4b`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/5fabd4bea95bafd2352102686357cc1db80813fd))
208
-
209
- ### Documentation
210
-
211
- - Update outdated text in docs
212
- ([`4f0693c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4f0693cae34b391d75884837e1ae6353a0501868))
PKG-INFO CHANGED
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bec_widgets
3
- Version: 1.9.0
3
+ Version: 1.10.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
@@ -7,6 +7,7 @@ from qtpy.QtWidgets import (
7
7
  QApplication,
8
8
  QGroupBox,
9
9
  QHBoxLayout,
10
+ QPushButton,
10
11
  QSplitter,
11
12
  QTabWidget,
12
13
  QVBoxLayout,
@@ -17,6 +18,7 @@ from bec_widgets.utils import BECDispatcher
17
18
  from bec_widgets.utils.colors import apply_theme
18
19
  from bec_widgets.widgets.containers.dock import BECDockArea
19
20
  from bec_widgets.widgets.containers.figure import BECFigure
21
+ from bec_widgets.widgets.containers.layout_manager.layout_manager import LayoutManagerWidget
20
22
  from bec_widgets.widgets.editors.jupyter_console.jupyter_console import BECJupyterConsole
21
23
 
22
24
 
@@ -50,11 +52,16 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
50
52
  "d1": self.d1,
51
53
  "d2": self.d2,
52
54
  "wave": self.wf,
53
- # "bar": self.bar,
54
- # "cm": self.colormap,
55
55
  "im": self.im,
56
56
  "mm": self.mm,
57
57
  "mw": self.mw,
58
+ "lm": self.lm,
59
+ "btn1": self.btn1,
60
+ "btn2": self.btn2,
61
+ "btn3": self.btn3,
62
+ "btn4": self.btn4,
63
+ "btn5": self.btn5,
64
+ "btn6": self.btn6,
58
65
  }
59
66
  )
60
67
 
@@ -79,11 +86,25 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
79
86
  second_tab_layout.addWidget(self.figure)
80
87
  tab_widget.addTab(second_tab, "BEC Figure")
81
88
 
89
+ third_tab = QWidget()
90
+ third_tab_layout = QVBoxLayout(third_tab)
91
+ self.lm = LayoutManagerWidget()
92
+ third_tab_layout.addWidget(self.lm)
93
+ tab_widget.addTab(third_tab, "Layout Manager Widget")
94
+
82
95
  group_box = QGroupBox("Jupyter Console", splitter)
83
96
  group_box_layout = QVBoxLayout(group_box)
84
97
  self.console = BECJupyterConsole(inprocess=True)
85
98
  group_box_layout.addWidget(self.console)
86
99
 
100
+ # Some buttons for layout testing
101
+ self.btn1 = QPushButton("Button 1")
102
+ self.btn2 = QPushButton("Button 2")
103
+ self.btn3 = QPushButton("Button 3")
104
+ self.btn4 = QPushButton("Button 4")
105
+ self.btn5 = QPushButton("Button 5")
106
+ self.btn6 = QPushButton("Button 6")
107
+
87
108
  # add stuff to figure
88
109
  self._init_figure()
89
110
 
@@ -93,15 +114,7 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
93
114
  self.setWindowTitle("Jupyter Console Window")
94
115
 
95
116
  def _init_figure(self):
96
- self.w1 = self.figure.plot(
97
- x_name="samx",
98
- y_name="bpm4i",
99
- # title="Standard Plot with sync device, custom labels - w1",
100
- # x_label="Motor Position",
101
- # y_label="Intensity (A.U.)",
102
- row=0,
103
- col=0,
104
- )
117
+ self.w1 = self.figure.plot(x_name="samx", y_name="bpm4i", row=0, col=0)
105
118
  self.w1.set(
106
119
  title="Standard Plot with sync device, custom labels - w1",
107
120
  x_label="Motor Position",
@@ -169,14 +182,6 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
169
182
  self.wf = self.d2.add_widget("BECFigure", row=0, col=0)
170
183
 
171
184
  self.mw = self.wf.multi_waveform(monitor="waveform") # , config=config)
172
- # self.wf.plot(x_name="samx", y_name="bpm3a")
173
- # self.wf.plot(x_name="samx", y_name="bpm4i", dap="GaussianModel")
174
- # self.bar = self.d2.add_widget("RingProgressBar", row=0, col=1)
175
- # self.bar.set_diameter(200)
176
-
177
- # self.d3 = self.dock.add_dock(name="dock_3", position="bottom")
178
- # self.colormap = pg.GradientWidget()
179
- # self.d3.add_widget(self.colormap, row=0, col=0)
180
185
 
181
186
  self.dock.save_state()
182
187
 
@@ -93,17 +93,24 @@ def patch_designer(): # pragma: no cover
93
93
  _extend_path_var("PATH", os.fspath(Path(sys._base_executable).parent), True)
94
94
  else:
95
95
  if sys.platform == "linux":
96
- suffix = f"{sys.abiflags}.so"
97
96
  env_var = "LD_PRELOAD"
97
+ current_pid = os.getpid()
98
+ with open(f"/proc/{current_pid}/maps", "rt") as f:
99
+ for line in f:
100
+ if "libpython" in line:
101
+ lib_path = line.split()[-1]
102
+ os.environ[env_var] = lib_path
103
+ break
104
+
98
105
  elif sys.platform == "darwin":
99
106
  suffix = ".dylib"
100
107
  env_var = "DYLD_INSERT_LIBRARIES"
108
+ version = f"{major_version}.{minor_version}"
109
+ library_name = f"libpython{version}{suffix}"
110
+ lib_path = str(Path(sysconfig.get_config_var("LIBDIR")) / library_name)
111
+ os.environ[env_var] = lib_path
101
112
  else:
102
113
  raise RuntimeError(f"Unsupported platform: {sys.platform}")
103
- version = f"{major_version}.{minor_version}"
104
- library_name = f"libpython{version}{suffix}"
105
- lib_path = str(Path(sysconfig.get_config_var("LIBDIR")) / library_name)
106
- os.environ[env_var] = lib_path
107
114
 
108
115
  if is_pyenv_python() or is_virtual_env():
109
116
  # append all editable packages to the PYTHONPATH
@@ -0,0 +1,881 @@
1
+ import math
2
+ import sys
3
+ from typing import Dict, Literal, Optional, Set, Tuple, Union
4
+
5
+ from qtpy.QtWidgets import (
6
+ QApplication,
7
+ QComboBox,
8
+ QGridLayout,
9
+ QGroupBox,
10
+ QHBoxLayout,
11
+ QLabel,
12
+ QLineEdit,
13
+ QMainWindow,
14
+ QMessageBox,
15
+ QPushButton,
16
+ QSpinBox,
17
+ QSplitter,
18
+ QVBoxLayout,
19
+ QWidget,
20
+ )
21
+ from typeguard import typechecked
22
+
23
+ from bec_widgets.cli.rpc_wigdet_handler import widget_handler
24
+
25
+
26
+ class LayoutManagerWidget(QWidget):
27
+ """
28
+ A robust layout manager that extends QGridLayout functionality, allowing
29
+ users to add/remove widgets, access widgets by coordinates, shift widgets,
30
+ and change the layout dynamically with automatic reindexing to keep the grid compact.
31
+
32
+ Supports adding widgets via QWidget instances or string identifiers referencing the widget handler.
33
+ """
34
+
35
+ def __init__(self, parent=None, auto_reindex=True):
36
+ super().__init__(parent)
37
+ self.layout = QGridLayout(self)
38
+ self.auto_reindex = auto_reindex
39
+
40
+ # Mapping from widget to its position (row, col, rowspan, colspan)
41
+ self.widget_positions: Dict[QWidget, Tuple[int, int, int, int]] = {}
42
+
43
+ # Mapping from (row, col) to widget
44
+ self.position_widgets: Dict[Tuple[int, int], QWidget] = {}
45
+
46
+ # Keep track of the current position for automatic placement
47
+ self.current_row = 0
48
+ self.current_col = 0
49
+
50
+ def add_widget(
51
+ self,
52
+ widget: QWidget | str,
53
+ row: int | None = None,
54
+ col: Optional[int] = None,
55
+ rowspan: int = 1,
56
+ colspan: int = 1,
57
+ shift_existing: bool = True,
58
+ shift_direction: Literal["down", "up", "left", "right"] = "right",
59
+ ) -> QWidget:
60
+ """
61
+ Add a widget to the grid with enhanced shifting capabilities.
62
+
63
+ Args:
64
+ widget (QWidget | str): The widget to add. If str, it is used to create a widget via widget_handler.
65
+ row (int, optional): The row to add the widget to. If None, the next available row is used.
66
+ col (int, optional): The column to add the widget to. If None, the next available column is used.
67
+ rowspan (int): Number of rows the widget spans. Default is 1.
68
+ colspan (int): Number of columns the widget spans. Default is 1.
69
+ shift_existing (bool): Whether to shift existing widgets if the target position is occupied. Default is True.
70
+ shift_direction (Literal["down", "up", "left", "right"]): Direction to shift existing widgets. Default is "right".
71
+
72
+ Returns:
73
+ QWidget: The widget that was added.
74
+ """
75
+ # Handle widget creation if a BECWidget string identifier is provided
76
+ if isinstance(widget, str):
77
+ widget = widget_handler.create_widget(widget)
78
+
79
+ if row is None:
80
+ row = self.current_row
81
+ if col is None:
82
+ col = self.current_col
83
+
84
+ if (row, col) in self.position_widgets:
85
+ if shift_existing:
86
+ # Attempt to shift the existing widget in the specified direction
87
+ self.shift_widgets(direction=shift_direction, start_row=row, start_col=col)
88
+ else:
89
+ raise ValueError(f"Position ({row}, {col}) is already occupied.")
90
+
91
+ # Add the widget to the layout
92
+ self.layout.addWidget(widget, row, col, rowspan, colspan)
93
+ self.widget_positions[widget] = (row, col, rowspan, colspan)
94
+ self.position_widgets[(row, col)] = widget
95
+
96
+ # Update current position for automatic placement
97
+ self.current_col = col + colspan
98
+ self.current_row = max(self.current_row, row)
99
+
100
+ if self.auto_reindex:
101
+ self.reindex_grid()
102
+
103
+ return widget
104
+
105
+ def add_widget_relative(
106
+ self,
107
+ widget: QWidget | str,
108
+ reference_widget: QWidget,
109
+ position: Literal["left", "right", "top", "bottom"],
110
+ rowspan: int = 1,
111
+ colspan: int = 1,
112
+ shift_existing: bool = True,
113
+ shift_direction: Literal["down", "up", "left", "right"] = "right",
114
+ ) -> QWidget:
115
+ """
116
+ Add a widget relative to an existing widget.
117
+
118
+ Args:
119
+ widget (QWidget | str): The widget to add. If str, it is used to create a widget via widget_handler.
120
+ reference_widget (QWidget): The widget relative to which the new widget will be placed.
121
+ position (Literal["left", "right", "top", "bottom"]): Position relative to the reference widget.
122
+ rowspan (int): Number of rows the widget spans. Default is 1.
123
+ colspan (int): Number of columns the widget spans. Default is 1.
124
+ shift_existing (bool): Whether to shift existing widgets if the target position is occupied.
125
+ shift_direction (Literal["down", "up", "left", "right"]): Direction to shift existing widgets.
126
+
127
+ Returns:
128
+ QWidget: The widget that was added.
129
+
130
+ Raises:
131
+ ValueError: If the reference widget is not found.
132
+ """
133
+ if reference_widget not in self.widget_positions:
134
+ raise ValueError("Reference widget not found in layout.")
135
+
136
+ ref_row, ref_col, ref_rowspan, ref_colspan = self.widget_positions[reference_widget]
137
+
138
+ # Determine new widget position based on the specified relative position
139
+ if position == "left":
140
+ new_row = ref_row
141
+ new_col = ref_col - 1
142
+ elif position == "right":
143
+ new_row = ref_row
144
+ new_col = ref_col + ref_colspan
145
+ elif position == "top":
146
+ new_row = ref_row - 1
147
+ new_col = ref_col
148
+ elif position == "bottom":
149
+ new_row = ref_row + ref_rowspan
150
+ new_col = ref_col
151
+ else:
152
+ raise ValueError("Invalid position. Choose from 'left', 'right', 'top', 'bottom'.")
153
+
154
+ # Add the widget at the calculated position
155
+ return self.add_widget(
156
+ widget=widget,
157
+ row=new_row,
158
+ col=new_col,
159
+ rowspan=rowspan,
160
+ colspan=colspan,
161
+ shift_existing=shift_existing,
162
+ shift_direction=shift_direction,
163
+ )
164
+
165
+ def move_widget_by_coords(
166
+ self,
167
+ current_row: int,
168
+ current_col: int,
169
+ new_row: int,
170
+ new_col: int,
171
+ shift: bool = True,
172
+ shift_direction: Literal["down", "up", "left", "right"] = "right",
173
+ ) -> None:
174
+ """
175
+ Move a widget from (current_row, current_col) to (new_row, new_col).
176
+
177
+ Args:
178
+ current_row (int): Current row of the widget.
179
+ current_col (int): Current column of the widget.
180
+ new_row (int): Target row.
181
+ new_col (int): Target column.
182
+ shift (bool): Whether to shift existing widgets if the target position is occupied.
183
+ shift_direction (Literal["down", "up", "left", "right"]): Direction to shift existing widgets.
184
+
185
+ Raises:
186
+ ValueError: If the widget is not found or target position is invalid.
187
+ """
188
+ self.move_widget(
189
+ old_row=current_row,
190
+ old_col=current_col,
191
+ new_row=new_row,
192
+ new_col=new_col,
193
+ shift=shift,
194
+ shift_direction=shift_direction,
195
+ )
196
+
197
+ @typechecked
198
+ def move_widget_by_object(
199
+ self,
200
+ widget: QWidget,
201
+ new_row: int,
202
+ new_col: int,
203
+ shift: bool = True,
204
+ shift_direction: Literal["down", "up", "left", "right"] = "right",
205
+ ) -> None:
206
+ """
207
+ Move a widget to a new position using the widget object.
208
+
209
+ Args:
210
+ widget (QWidget): The widget to move.
211
+ new_row (int): Target row.
212
+ new_col (int): Target column.
213
+ shift (bool): Whether to shift existing widgets if the target position is occupied.
214
+ shift_direction (Literal["down", "up", "left", "right"]): Direction to shift existing widgets.
215
+
216
+ Raises:
217
+ ValueError: If the widget is not found or target position is invalid.
218
+ """
219
+ if widget not in self.widget_positions:
220
+ raise ValueError("Widget not found in layout.")
221
+
222
+ old_position = self.widget_positions[widget]
223
+ old_row, old_col = old_position[0], old_position[1]
224
+
225
+ self.move_widget(
226
+ old_row=old_row,
227
+ old_col=old_col,
228
+ new_row=new_row,
229
+ new_col=new_col,
230
+ shift=shift,
231
+ shift_direction=shift_direction,
232
+ )
233
+
234
+ @typechecked
235
+ def move_widget(
236
+ self,
237
+ old_row: int | None = None,
238
+ old_col: int | None = None,
239
+ new_row: int | None = None,
240
+ new_col: int | None = None,
241
+ shift: bool = True,
242
+ shift_direction: Literal["down", "up", "left", "right"] = "right",
243
+ ) -> None:
244
+ """
245
+ Move a widget to a new position. If the new position is occupied and shift is True,
246
+ shift the existing widget to the specified direction.
247
+
248
+ Args:
249
+ old_row (int, optional): The current row of the widget.
250
+ old_col (int, optional): The current column of the widget.
251
+ new_row (int, optional): The target row to move the widget to.
252
+ new_col (int, optional): The target column to move the widget to.
253
+ shift (bool): Whether to shift existing widgets if the target position is occupied.
254
+ shift_direction (Literal["down", "up", "left", "right"]): Direction to shift existing widgets.
255
+
256
+ Raises:
257
+ ValueError: If the widget is not found or target position is invalid.
258
+ """
259
+ if new_row is None or new_col is None:
260
+ raise ValueError("Must provide both new_row and new_col to move a widget.")
261
+
262
+ if old_row is None and old_col is None:
263
+ raise ValueError(f"No widget found at position ({old_row}, {old_col}).")
264
+ widget = self.get_widget(old_row, old_col)
265
+
266
+ if (new_row, new_col) in self.position_widgets:
267
+ if not shift:
268
+ raise ValueError(f"Position ({new_row}, {new_col}) is already occupied.")
269
+ # Shift the existing widget to make space
270
+ self.shift_widgets(
271
+ direction=shift_direction,
272
+ start_row=new_row if shift_direction in ["down", "up"] else 0,
273
+ start_col=new_col if shift_direction in ["left", "right"] else 0,
274
+ )
275
+
276
+ # Proceed to move the widget
277
+ self.layout.removeWidget(widget)
278
+ old_position = self.widget_positions.pop(widget)
279
+ self.position_widgets.pop((old_position[0], old_position[1]))
280
+
281
+ self.layout.addWidget(widget, new_row, new_col, old_position[2], old_position[3])
282
+ self.widget_positions[widget] = (new_row, new_col, old_position[2], old_position[3])
283
+ self.position_widgets[(new_row, new_col)] = widget
284
+
285
+ # Update current_row and current_col for automatic placement if needed
286
+ self.current_row = max(self.current_row, new_row)
287
+ self.current_col = max(self.current_col, new_col + old_position[3])
288
+
289
+ if self.auto_reindex:
290
+ self.reindex_grid()
291
+
292
+ @typechecked
293
+ def shift_widgets(
294
+ self,
295
+ direction: Literal["down", "up", "left", "right"],
296
+ start_row: int = 0,
297
+ start_col: int = 0,
298
+ ) -> None:
299
+ """
300
+ Shift widgets in the grid in the specified direction starting from the given position.
301
+
302
+ Args:
303
+ direction (Literal["down", "up", "left", "right"]): Direction to shift widgets.
304
+ start_row (int): Starting row index.
305
+ start_col (int): Starting column index.
306
+
307
+ Raises:
308
+ ValueError: If shifting causes widgets to go out of grid boundaries.
309
+ """
310
+ shifts = []
311
+ positions_to_shift = [(start_row, start_col)]
312
+ visited_positions = set()
313
+
314
+ while positions_to_shift:
315
+ row, col = positions_to_shift.pop(0)
316
+ if (row, col) in visited_positions:
317
+ continue
318
+ visited_positions.add((row, col))
319
+
320
+ widget = self.position_widgets.get((row, col))
321
+ if widget is None:
322
+ continue # No widget at this position
323
+
324
+ # Compute new position based on the direction
325
+ if direction == "down":
326
+ new_row = row + 1
327
+ new_col = col
328
+ elif direction == "up":
329
+ new_row = row - 1
330
+ new_col = col
331
+ elif direction == "right":
332
+ new_row = row
333
+ new_col = col + 1
334
+ elif direction == "left":
335
+ new_row = row
336
+ new_col = col - 1
337
+
338
+ # Check for negative indices
339
+ if new_row < 0 or new_col < 0:
340
+ raise ValueError("Shifting widgets out of grid boundaries.")
341
+
342
+ # If the new position is occupied, add it to the positions to shift
343
+ if (new_row, new_col) in self.position_widgets:
344
+ positions_to_shift.append((new_row, new_col))
345
+
346
+ shifts.append(
347
+ (widget, (row, col), (new_row, new_col), self.widget_positions[widget][2:])
348
+ )
349
+
350
+ # Remove all widgets from their old positions
351
+ for widget, (old_row, old_col), _, _ in shifts:
352
+ self.layout.removeWidget(widget)
353
+ self.position_widgets.pop((old_row, old_col))
354
+
355
+ # Add widgets to their new positions
356
+ for widget, _, (new_row, new_col), (rowspan, colspan) in shifts:
357
+ self.layout.addWidget(widget, new_row, new_col, rowspan, colspan)
358
+ self.widget_positions[widget] = (new_row, new_col, rowspan, colspan)
359
+ self.position_widgets[(new_row, new_col)] = widget
360
+
361
+ # Update current_row and current_col if needed
362
+ self.current_row = max(self.current_row, new_row)
363
+ self.current_col = max(self.current_col, new_col + colspan)
364
+
365
+ def shift_all_widgets(self, direction: Literal["down", "up", "left", "right"]) -> None:
366
+ """
367
+ Shift all widgets in the grid in the specified direction to make room and prevent negative indices.
368
+
369
+ Args:
370
+ direction (Literal["down", "up", "left", "right"]): Direction to shift all widgets.
371
+ """
372
+ # First, collect all the shifts to perform
373
+ shifts = []
374
+ for widget, (row, col, rowspan, colspan) in self.widget_positions.items():
375
+
376
+ if direction == "down":
377
+ new_row = row + 1
378
+ new_col = col
379
+ elif direction == "up":
380
+ new_row = row - 1
381
+ new_col = col
382
+ elif direction == "right":
383
+ new_row = row
384
+ new_col = col + 1
385
+ elif direction == "left":
386
+ new_row = row
387
+ new_col = col - 1
388
+
389
+ # Check for negative indices
390
+ if new_row < 0 or new_col < 0:
391
+ raise ValueError("Shifting widgets out of grid boundaries.")
392
+
393
+ shifts.append((widget, (row, col), (new_row, new_col), (rowspan, colspan)))
394
+
395
+ # Now perform the shifts
396
+ for widget, (old_row, old_col), (new_row, new_col), (rowspan, colspan) in shifts:
397
+ self.layout.removeWidget(widget)
398
+ self.position_widgets.pop((old_row, old_col))
399
+
400
+ for widget, (old_row, old_col), (new_row, new_col), (rowspan, colspan) in shifts:
401
+ self.layout.addWidget(widget, new_row, new_col, rowspan, colspan)
402
+ self.widget_positions[widget] = (new_row, new_col, rowspan, colspan)
403
+ self.position_widgets[(new_row, new_col)] = widget
404
+
405
+ # Update current_row and current_col based on new widget positions
406
+ self.current_row = max((pos[0] for pos in self.position_widgets.keys()), default=0)
407
+ self.current_col = max((pos[1] for pos in self.position_widgets.keys()), default=0)
408
+
409
+ def remove(
410
+ self,
411
+ row: Optional[int] = None,
412
+ col: Optional[int] = None,
413
+ coordinates: Optional[Tuple[int, int]] = None,
414
+ ) -> None:
415
+ """
416
+ Remove a widget from the layout. Can be removed by widget ID or by coordinates.
417
+
418
+ Args:
419
+ row (int, optional): The row coordinate of the widget to remove.
420
+ col (int, optional): The column coordinate of the widget to remove.
421
+ coordinates (tuple[int, int], optional): The (row, col) coordinates of the widget to remove.
422
+
423
+ Raises:
424
+ ValueError: If the widget to remove is not found.
425
+ """
426
+ if coordinates:
427
+ row, col = coordinates
428
+ widget = self.get_widget(row, col)
429
+ if widget is None:
430
+ raise ValueError(f"No widget found at coordinates {coordinates}.")
431
+ elif row is not None and col is not None:
432
+ widget = self.get_widget(row, col)
433
+ if widget is None:
434
+ raise ValueError(f"No widget found at position ({row}, {col}).")
435
+ else:
436
+ raise ValueError(
437
+ "Must provide either widget_id, coordinates, or both row and col for removal."
438
+ )
439
+
440
+ self.remove_widget(widget)
441
+
442
+ def remove_widget(self, widget: QWidget) -> None:
443
+ """
444
+ Remove a widget from the grid and reindex the grid to keep it compact.
445
+
446
+ Args:
447
+ widget (QWidget): The widget to remove.
448
+
449
+ Raises:
450
+ ValueError: If the widget is not found in the layout.
451
+ """
452
+ if widget not in self.widget_positions:
453
+ raise ValueError("Widget not found in layout.")
454
+
455
+ position = self.widget_positions.pop(widget)
456
+ self.position_widgets.pop((position[0], position[1]))
457
+ self.layout.removeWidget(widget)
458
+ widget.setParent(None) # Remove widget from the parent
459
+ widget.deleteLater()
460
+
461
+ # Reindex the grid to maintain compactness
462
+ if self.auto_reindex:
463
+ self.reindex_grid()
464
+
465
+ def get_widget(self, row: int, col: int) -> QWidget | None:
466
+ """
467
+ Get the widget at the specified position.
468
+
469
+ Args:
470
+ row (int): The row coordinate.
471
+ col (int): The column coordinate.
472
+
473
+ Returns:
474
+ QWidget | None: The widget at the specified position, or None if empty.
475
+ """
476
+ return self.position_widgets.get((row, col))
477
+
478
+ def get_widget_position(self, widget: QWidget) -> Tuple[int, int, int, int] | None:
479
+ """
480
+ Get the position of the specified widget.
481
+
482
+ Args:
483
+ widget (QWidget): The widget to query.
484
+
485
+ Returns:
486
+ Tuple[int, int, int, int] | None: The (row, col, rowspan, colspan) tuple, or None if not found.
487
+ """
488
+ return self.widget_positions.get(widget)
489
+
490
+ def change_layout(self, num_rows: int | None = None, num_cols: int | None = None) -> None:
491
+ """
492
+ Change the layout to have a certain number of rows and/or columns,
493
+ rearranging the widgets accordingly.
494
+
495
+ If only one of num_rows or num_cols is provided, the other is calculated automatically
496
+ based on the number of widgets and the provided constraint.
497
+
498
+ If both are provided, num_rows is calculated based on num_cols.
499
+
500
+ Args:
501
+ num_rows (int | None): The new maximum number of rows.
502
+ num_cols (int | None): The new maximum number of columns.
503
+ """
504
+ if num_rows is None and num_cols is None:
505
+ return # Nothing to change
506
+
507
+ total_widgets = len(self.widget_positions)
508
+
509
+ if num_cols is not None:
510
+ # Calculate num_rows based on num_cols
511
+ num_rows = math.ceil(total_widgets / num_cols)
512
+ elif num_rows is not None:
513
+ # Calculate num_cols based on num_rows
514
+ num_cols = math.ceil(total_widgets / num_rows)
515
+
516
+ # Sort widgets by current position (row-major order)
517
+ widgets_sorted = sorted(
518
+ self.widget_positions.items(),
519
+ key=lambda item: (item[1][0], item[1][1]), # Sort by row, then column
520
+ )
521
+
522
+ # Clear the layout without deleting widgets
523
+ for widget, _ in widgets_sorted:
524
+ self.layout.removeWidget(widget)
525
+
526
+ # Reset position mappings
527
+ self.widget_positions.clear()
528
+ self.position_widgets.clear()
529
+
530
+ # Re-add widgets based on new layout constraints
531
+ current_row, current_col = 0, 0
532
+ for widget, _ in widgets_sorted:
533
+ if current_col >= num_cols:
534
+ current_col = 0
535
+ current_row += 1
536
+ self.layout.addWidget(widget, current_row, current_col, 1, 1)
537
+ self.widget_positions[widget] = (current_row, current_col, 1, 1)
538
+ self.position_widgets[(current_row, current_col)] = widget
539
+ current_col += 1
540
+
541
+ # Update current_row and current_col for automatic placement
542
+ self.current_row = current_row
543
+ self.current_col = current_col
544
+
545
+ # Reindex the grid to ensure compactness
546
+ self.reindex_grid()
547
+
548
+ def clear_layout(self) -> None:
549
+ """
550
+ Remove all widgets from the layout without deleting them.
551
+ """
552
+ for widget in list(self.widget_positions):
553
+ self.layout.removeWidget(widget)
554
+ self.position_widgets.pop(
555
+ (self.widget_positions[widget][0], self.widget_positions[widget][1])
556
+ )
557
+ self.widget_positions.pop(widget)
558
+ widget.setParent(None) # Optionally hide/remove the widget
559
+
560
+ self.current_row = 0
561
+ self.current_col = 0
562
+
563
+ def reindex_grid(self) -> None:
564
+ """
565
+ Reindex the grid to remove empty rows and columns, ensuring that
566
+ widget coordinates are contiguous and start from (0, 0).
567
+ """
568
+ # Step 1: Collect all occupied positions
569
+ occupied_positions = sorted(self.position_widgets.keys())
570
+
571
+ if not occupied_positions:
572
+ # No widgets to reindex
573
+ self.clear_layout()
574
+ return
575
+
576
+ # Step 2: Determine the new mapping by eliminating empty columns and rows
577
+ # Find unique rows and columns
578
+ unique_rows = sorted(set(pos[0] for pos in occupied_positions))
579
+ unique_cols = sorted(set(pos[1] for pos in occupied_positions))
580
+
581
+ # Create mappings from old to new indices
582
+ row_mapping = {old_row: new_row for new_row, old_row in enumerate(unique_rows)}
583
+ col_mapping = {old_col: new_col for new_col, old_col in enumerate(unique_cols)}
584
+
585
+ # Step 3: Collect widgets with their new positions
586
+ widgets_with_new_positions = []
587
+ for widget, (row, col, rowspan, colspan) in self.widget_positions.items():
588
+ new_row = row_mapping[row]
589
+ new_col = col_mapping[col]
590
+ widgets_with_new_positions.append((widget, new_row, new_col, rowspan, colspan))
591
+
592
+ # Step 4: Clear the layout and reset mappings
593
+ self.clear_layout()
594
+
595
+ # Reset current_row and current_col
596
+ self.current_row = 0
597
+ self.current_col = 0
598
+
599
+ # Step 5: Re-add widgets with new positions
600
+ for widget, new_row, new_col, rowspan, colspan in widgets_with_new_positions:
601
+ self.layout.addWidget(widget, new_row, new_col, rowspan, colspan)
602
+ self.widget_positions[widget] = (new_row, new_col, rowspan, colspan)
603
+ self.position_widgets[(new_row, new_col)] = widget
604
+
605
+ # Update current position for automatic placement
606
+ self.current_col = max(self.current_col, new_col + colspan)
607
+ self.current_row = max(self.current_row, new_row)
608
+
609
+ def get_widgets_positions(self) -> Dict[QWidget, Tuple[int, int, int, int]]:
610
+ """
611
+ Get the positions of all widgets in the layout.
612
+
613
+ Returns:
614
+ Dict[QWidget, Tuple[int, int, int, int]]: Mapping of widgets to their (row, col, rowspan, colspan).
615
+ """
616
+ return self.widget_positions.copy()
617
+
618
+ def print_all_button_text(self):
619
+ """Debug function to print the text of all QPushButton widgets."""
620
+ print("Coordinates - Button Text")
621
+ for coord, widget in self.position_widgets.items():
622
+ if isinstance(widget, QPushButton):
623
+ print(f"{coord} - {widget.text()}")
624
+
625
+
626
+ ####################################################################################################
627
+ # The following code is for the GUI control panel to interact with the LayoutManagerWidget.
628
+ # It is not covered by any tests as it serves only as an example for the LayoutManagerWidget class.
629
+ ####################################################################################################
630
+
631
+
632
+ class ControlPanel(QWidget): # pragma: no cover
633
+ def __init__(self, layout_manager: LayoutManagerWidget):
634
+ super().__init__()
635
+ self.layout_manager = layout_manager
636
+ self.init_ui()
637
+
638
+ def init_ui(self):
639
+ main_layout = QVBoxLayout()
640
+
641
+ # Add Widget by Coordinates
642
+ add_coord_group = QGroupBox("Add Widget by Coordinates")
643
+ add_coord_layout = QGridLayout()
644
+
645
+ add_coord_layout.addWidget(QLabel("Text:"), 0, 0)
646
+ self.text_input = QLineEdit()
647
+ add_coord_layout.addWidget(self.text_input, 0, 1)
648
+
649
+ add_coord_layout.addWidget(QLabel("Row:"), 1, 0)
650
+ self.row_input = QSpinBox()
651
+ self.row_input.setMinimum(0)
652
+ add_coord_layout.addWidget(self.row_input, 1, 1)
653
+
654
+ add_coord_layout.addWidget(QLabel("Column:"), 2, 0)
655
+ self.col_input = QSpinBox()
656
+ self.col_input.setMinimum(0)
657
+ add_coord_layout.addWidget(self.col_input, 2, 1)
658
+
659
+ self.add_button = QPushButton("Add at Coordinates")
660
+ self.add_button.clicked.connect(self.add_at_coordinates)
661
+ add_coord_layout.addWidget(self.add_button, 3, 0, 1, 2)
662
+
663
+ add_coord_group.setLayout(add_coord_layout)
664
+ main_layout.addWidget(add_coord_group)
665
+
666
+ # Add Widget Relative
667
+ add_rel_group = QGroupBox("Add Widget Relative to Existing")
668
+ add_rel_layout = QGridLayout()
669
+
670
+ add_rel_layout.addWidget(QLabel("Text:"), 0, 0)
671
+ self.rel_text_input = QLineEdit()
672
+ add_rel_layout.addWidget(self.rel_text_input, 0, 1)
673
+
674
+ add_rel_layout.addWidget(QLabel("Reference Widget:"), 1, 0)
675
+ self.ref_widget_combo = QComboBox()
676
+ add_rel_layout.addWidget(self.ref_widget_combo, 1, 1)
677
+
678
+ add_rel_layout.addWidget(QLabel("Position:"), 2, 0)
679
+ self.position_combo = QComboBox()
680
+ self.position_combo.addItems(["left", "right", "top", "bottom"])
681
+ add_rel_layout.addWidget(self.position_combo, 2, 1)
682
+
683
+ self.add_rel_button = QPushButton("Add Relative")
684
+ self.add_rel_button.clicked.connect(self.add_relative)
685
+ add_rel_layout.addWidget(self.add_rel_button, 3, 0, 1, 2)
686
+
687
+ add_rel_group.setLayout(add_rel_layout)
688
+ main_layout.addWidget(add_rel_group)
689
+
690
+ # Remove Widget
691
+ remove_group = QGroupBox("Remove Widget")
692
+ remove_layout = QGridLayout()
693
+
694
+ remove_layout.addWidget(QLabel("Row:"), 0, 0)
695
+ self.remove_row_input = QSpinBox()
696
+ self.remove_row_input.setMinimum(0)
697
+ remove_layout.addWidget(self.remove_row_input, 0, 1)
698
+
699
+ remove_layout.addWidget(QLabel("Column:"), 1, 0)
700
+ self.remove_col_input = QSpinBox()
701
+ self.remove_col_input.setMinimum(0)
702
+ remove_layout.addWidget(self.remove_col_input, 1, 1)
703
+
704
+ self.remove_button = QPushButton("Remove at Coordinates")
705
+ self.remove_button.clicked.connect(self.remove_widget)
706
+ remove_layout.addWidget(self.remove_button, 2, 0, 1, 2)
707
+
708
+ remove_group.setLayout(remove_layout)
709
+ main_layout.addWidget(remove_group)
710
+
711
+ # Change Layout
712
+ change_layout_group = QGroupBox("Change Layout")
713
+ change_layout_layout = QGridLayout()
714
+
715
+ change_layout_layout.addWidget(QLabel("Number of Rows:"), 0, 0)
716
+ self.change_rows_input = QSpinBox()
717
+ self.change_rows_input.setMinimum(1)
718
+ self.change_rows_input.setValue(1) # Default value
719
+ change_layout_layout.addWidget(self.change_rows_input, 0, 1)
720
+
721
+ change_layout_layout.addWidget(QLabel("Number of Columns:"), 1, 0)
722
+ self.change_cols_input = QSpinBox()
723
+ self.change_cols_input.setMinimum(1)
724
+ self.change_cols_input.setValue(1) # Default value
725
+ change_layout_layout.addWidget(self.change_cols_input, 1, 1)
726
+
727
+ self.change_layout_button = QPushButton("Apply Layout Change")
728
+ self.change_layout_button.clicked.connect(self.change_layout)
729
+ change_layout_layout.addWidget(self.change_layout_button, 2, 0, 1, 2)
730
+
731
+ change_layout_group.setLayout(change_layout_layout)
732
+ main_layout.addWidget(change_layout_group)
733
+
734
+ # Remove All Widgets
735
+ self.clear_all_button = QPushButton("Clear All Widgets")
736
+ self.clear_all_button.clicked.connect(self.clear_all_widgets)
737
+ main_layout.addWidget(self.clear_all_button)
738
+
739
+ # Refresh Reference Widgets and Print Button
740
+ self.refresh_button = QPushButton("Refresh Reference Widgets")
741
+ self.refresh_button.clicked.connect(self.refresh_references)
742
+ self.print_button = QPushButton("Print All Button Text")
743
+ self.print_button.clicked.connect(self.layout_manager.print_all_button_text)
744
+ main_layout.addWidget(self.refresh_button)
745
+ main_layout.addWidget(self.print_button)
746
+
747
+ main_layout.addStretch()
748
+ self.setLayout(main_layout)
749
+ self.refresh_references()
750
+
751
+ def refresh_references(self):
752
+ self.ref_widget_combo.clear()
753
+ widgets = self.layout_manager.get_widgets_positions()
754
+ for widget in widgets:
755
+ if isinstance(widget, QPushButton):
756
+ self.ref_widget_combo.addItem(widget.text(), widget)
757
+
758
+ def add_at_coordinates(self):
759
+ text = self.text_input.text()
760
+ row = self.row_input.value()
761
+ col = self.col_input.value()
762
+
763
+ if not text:
764
+ QMessageBox.warning(self, "Input Error", "Please enter text for the button.")
765
+ return
766
+
767
+ button = QPushButton(text)
768
+ try:
769
+ self.layout_manager.add_widget(widget=button, row=row, col=col)
770
+ self.refresh_references()
771
+ except Exception as e:
772
+ QMessageBox.critical(self, "Error", str(e))
773
+
774
+ def add_relative(self):
775
+ text = self.rel_text_input.text()
776
+ ref_index = self.ref_widget_combo.currentIndex()
777
+ ref_widget = self.ref_widget_combo.itemData(ref_index)
778
+ position = self.position_combo.currentText()
779
+
780
+ if not text:
781
+ QMessageBox.warning(self, "Input Error", "Please enter text for the button.")
782
+ return
783
+
784
+ if ref_widget is None:
785
+ QMessageBox.warning(self, "Input Error", "Please select a reference widget.")
786
+ return
787
+
788
+ button = QPushButton(text)
789
+ try:
790
+ self.layout_manager.add_widget_relative(
791
+ widget=button, reference_widget=ref_widget, position=position
792
+ )
793
+ self.refresh_references()
794
+ except Exception as e:
795
+ QMessageBox.critical(self, "Error", str(e))
796
+
797
+ def remove_widget(self):
798
+ row = self.remove_row_input.value()
799
+ col = self.remove_col_input.value()
800
+
801
+ try:
802
+ widget = self.layout_manager.get_widget(row, col)
803
+ if widget is None:
804
+ QMessageBox.warning(self, "Not Found", f"No widget found at ({row}, {col}).")
805
+ return
806
+ self.layout_manager.remove_widget(widget)
807
+ self.refresh_references()
808
+ except Exception as e:
809
+ QMessageBox.critical(self, "Error", str(e))
810
+
811
+ def change_layout(self):
812
+ num_rows = self.change_rows_input.value()
813
+ num_cols = self.change_cols_input.value()
814
+
815
+ try:
816
+ self.layout_manager.change_layout(num_rows=num_rows, num_cols=num_cols)
817
+ self.refresh_references()
818
+ except Exception as e:
819
+ QMessageBox.critical(self, "Error", str(e))
820
+
821
+ def clear_all_widgets(self):
822
+ reply = QMessageBox.question(
823
+ self,
824
+ "Confirm Clear",
825
+ "Are you sure you want to remove all widgets?",
826
+ QMessageBox.Yes | QMessageBox.No,
827
+ QMessageBox.No,
828
+ )
829
+
830
+ if reply == QMessageBox.Yes:
831
+ try:
832
+ self.layout_manager.clear_layout()
833
+ self.refresh_references()
834
+ except Exception as e:
835
+ QMessageBox.critical(self, "Error", str(e))
836
+
837
+
838
+ class MainWindow(QMainWindow): # pragma: no cover
839
+ def __init__(self):
840
+ super().__init__()
841
+ self.setWindowTitle("Layout Manager Demo")
842
+ self.resize(800, 600)
843
+ self.init_ui()
844
+
845
+ def init_ui(self):
846
+ central_widget = QWidget()
847
+ main_layout = QHBoxLayout()
848
+
849
+ # Layout Area GroupBox
850
+ layout_group = QGroupBox("Layout Area")
851
+ layout_group.setMinimumSize(400, 400)
852
+ layout_layout = QVBoxLayout()
853
+
854
+ self.layout_manager = LayoutManagerWidget()
855
+ layout_layout.addWidget(self.layout_manager)
856
+
857
+ layout_group.setLayout(layout_layout)
858
+
859
+ # Splitter
860
+ splitter = QSplitter()
861
+ splitter.addWidget(layout_group)
862
+
863
+ # Control Panel
864
+ control_panel = ControlPanel(self.layout_manager)
865
+ control_group = QGroupBox("Control Panel")
866
+ control_layout = QVBoxLayout()
867
+ control_layout.addWidget(control_panel)
868
+ control_layout.addStretch()
869
+ control_group.setLayout(control_layout)
870
+ splitter.addWidget(control_group)
871
+
872
+ main_layout.addWidget(splitter)
873
+ central_widget.setLayout(main_layout)
874
+ self.setCentralWidget(central_widget)
875
+
876
+
877
+ if __name__ == "__main__": # pragma: no cover
878
+ app = QApplication(sys.argv)
879
+ window = MainWindow()
880
+ window.show()
881
+ sys.exit(app.exec_())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bec_widgets
3
- Version: 1.9.0
3
+ Version: 1.10.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
@@ -2,11 +2,11 @@
2
2
  .gitlab-ci.yml,sha256=bAWGX_NR9rQZmv_bmyLXkEMRreWp0JzVNpsNTxk0NwE,8637
3
3
  .pylintrc,sha256=eeY8YwSI74oFfq6IYIbCqnx3Vk8ZncKaatv96n_Y8Rs,18544
4
4
  .readthedocs.yaml,sha256=aSOc277LqXcsTI6lgvm_JY80lMlr69GbPKgivua2cS0,603
5
- CHANGELOG.md,sha256=vk99JDb0HVTJZsEm6TJ8g-RemuqHi7tQsDKkk-GVF2k,7866
5
+ CHANGELOG.md,sha256=IKF4CsJODVEwtIjKM2zTTS9m_JRHCY3XRYlUdJ4DZLs,7686
6
6
  LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
7
- PKG-INFO,sha256=QjMuyUkfdYQ7l0gazbGYeGbf96Sx3xRfbSaBsM9rcrY,1308
7
+ PKG-INFO,sha256=hICH58v6kexJp6j2db_OOpSQQlaMAO57Af9ScSPDgSg,1309
8
8
  README.md,sha256=Od69x-RS85Hph0-WwWACwal4yUd67XkEn4APEfHhHFw,2649
9
- pyproject.toml,sha256=_ntcDbeIJWyJtMIlP5r3Hq9h1z4vuth-KYYJyK14PF0,2586
9
+ pyproject.toml,sha256=D6UU8tgbCO_3ovaiysqzl6EAp0qc_nHHOBav0gKDFvY,2587
10
10
  .git_hooks/pre-commit,sha256=n3RofIZHJl8zfJJIUomcMyYGFi_rwq4CC19z0snz3FI,286
11
11
  .gitlab/issue_templates/bug_report_template.md,sha256=gAuyEwl7XlnebBrkiJ9AqffSNOywmr8vygUFWKTuQeI,386
12
12
  .gitlab/issue_templates/documentation_update_template.md,sha256=FHLdb3TS_D9aL4CYZCjyXSulbaW5mrN2CmwTaeLPbNw,860
@@ -36,7 +36,7 @@ bec_widgets/examples/general_app/general_app.py,sha256=PoFCTuA_1yqrpgthASpYFgH7J
36
36
  bec_widgets/examples/general_app/general_app.ui,sha256=TsejkM3Z8znnixyqxoj4SwhIIpIzTAuGAMkGXSS1aT8,10479
37
37
  bec_widgets/examples/general_app/web_links.py,sha256=d5OgzgI9zb-NAC0pOGanOtJX3nZoe4x8QuQTw-_hK_8,434
38
38
  bec_widgets/examples/jupyter_console/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
- bec_widgets/examples/jupyter_console/jupyter_console_window.py,sha256=R_HiM0Js7RZX-uYSUK7TQ9Ao6UhrujIi84NzD_02Zzc,6876
39
+ bec_widgets/examples/jupyter_console/jupyter_console_window.py,sha256=mXU6G2PgbxOIEnHxn-PHJMkthAAh9kgVCiOwHQNozl0,7075
40
40
  bec_widgets/examples/plugin_example_pyside/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
41
  bec_widgets/examples/plugin_example_pyside/main.py,sha256=zDP5wO7wb3BVsQ15HOCRT1nNmCujIVRvSXZ3Txje8L0,549
42
42
  bec_widgets/examples/plugin_example_pyside/registertictactoe.py,sha256=cVhBnP0qx5j9Jft-VeKvlTFE-bX58hbP45CX0f4r5pM,535
@@ -57,7 +57,7 @@ bec_widgets/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
57
57
  bec_widgets/tests/utils.py,sha256=D1v3JLzzbnX3HXBQCjoFlNz5cYhjqrRkFcjx3yptMJA,6687
58
58
  bec_widgets/utils/__init__.py,sha256=1930ji1Jj6dVuY81Wd2kYBhHYNV-2R0bN_L4o9zBj1U,533
59
59
  bec_widgets/utils/bec_connector.py,sha256=g40KPxhfuSHLlAa6dyO1PtC-9cQpN8ZGgn_IAEU_xD4,10070
60
- bec_widgets/utils/bec_designer.py,sha256=Z3MeMju-KmTz8POtm23VQfp4rvtD2sF6eIOKQkl2F7w,4729
60
+ bec_widgets/utils/bec_designer.py,sha256=XBy38NbNMoRDpvRx5lGP2XnJNG34YKZ7I-ARFkn-gzs,5017
61
61
  bec_widgets/utils/bec_dispatcher.py,sha256=OFmkx9vOz4pA4Sdc14QreyDZ870QYskJ4B5daVVeYg4,6325
62
62
  bec_widgets/utils/bec_signal_proxy.py,sha256=PKJ7v8pKrAaqA9XNDMZZBlhVtEInX-ae6_0m2cQhiEw,2107
63
63
  bec_widgets/utils/bec_table.py,sha256=nA2b8ukSeUfquFMAxGrUVOqdrzMoDYD6O_4EYbOG2zk,717
@@ -108,6 +108,8 @@ bec_widgets/widgets/containers/figure/plots/multi_waveform/multi_waveform.py,sha
108
108
  bec_widgets/widgets/containers/figure/plots/waveform/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
109
109
  bec_widgets/widgets/containers/figure/plots/waveform/waveform.py,sha256=6j-3hg0tVtpCnDgbYObTYwiNI7ciuWgQ5L1TlAN0Kg8,57543
110
110
  bec_widgets/widgets/containers/figure/plots/waveform/waveform_curve.py,sha256=9rOFHIxRjz0-G6f-mpw0FNmX846ZPwGn8yrJ3FpxlVc,8725
111
+ bec_widgets/widgets/containers/layout_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
112
+ bec_widgets/widgets/containers/layout_manager/layout_manager.py,sha256=98G91ffPynoZTTi2IjklS9TwZh_JZbJX1_MeXAB2eC4,33766
111
113
  bec_widgets/widgets/containers/main_window/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
112
114
  bec_widgets/widgets/containers/main_window/main_window.py,sha256=A2IAux-lqupMCVZOQu7AUt1AEaeIG3Eza85eN0bknlI,272
113
115
  bec_widgets/widgets/control/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -315,8 +317,8 @@ bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.py,sha256=Z
315
317
  bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.pyproject,sha256=Lbi9zb6HNlIq14k6hlzR-oz6PIFShBuF7QxE6d87d64,34
316
318
  bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button_plugin.py,sha256=CzChz2SSETYsR8-36meqWnsXCT-FIy_J_xeU5coWDY8,1350
317
319
  bec_widgets/widgets/utility/visual/dark_mode_button/register_dark_mode_button.py,sha256=rMpZ1CaoucwobgPj1FuKTnt07W82bV1GaSYdoqcdMb8,521
318
- bec_widgets-1.9.0.dist-info/METADATA,sha256=QjMuyUkfdYQ7l0gazbGYeGbf96Sx3xRfbSaBsM9rcrY,1308
319
- bec_widgets-1.9.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
320
- bec_widgets-1.9.0.dist-info/entry_points.txt,sha256=dItMzmwA1wizJ1Itx15qnfJ0ZzKVYFLVJ1voxT7K7D4,214
321
- bec_widgets-1.9.0.dist-info/licenses/LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
322
- bec_widgets-1.9.0.dist-info/RECORD,,
320
+ bec_widgets-1.10.0.dist-info/METADATA,sha256=hICH58v6kexJp6j2db_OOpSQQlaMAO57Af9ScSPDgSg,1309
321
+ bec_widgets-1.10.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
322
+ bec_widgets-1.10.0.dist-info/entry_points.txt,sha256=dItMzmwA1wizJ1Itx15qnfJ0ZzKVYFLVJ1voxT7K7D4,214
323
+ bec_widgets-1.10.0.dist-info/licenses/LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
324
+ bec_widgets-1.10.0.dist-info/RECORD,,
pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "bec_widgets"
7
- version = "1.9.0"
7
+ version = "1.10.0"
8
8
  description = "BEC Widgets"
9
9
  requires-python = ">=3.10"
10
10
  classifiers = [