bec-widgets 0.54.0__py3-none-any.whl → 0.56.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 (51) hide show
  1. .gitlab-ci.yml +113 -8
  2. CHANGELOG.md +32 -21
  3. PKG-INFO +3 -1
  4. bec_widgets/cli/client.py +252 -0
  5. bec_widgets/cli/generate_cli.py +4 -1
  6. bec_widgets/cli/rpc_wigdet_handler.py +2 -1
  7. bec_widgets/examples/jupyter_console/jupyter_console_window.py +29 -37
  8. bec_widgets/examples/motor_movement/motor_control_compilations.py +1 -7
  9. bec_widgets/utils/__init__.py +1 -0
  10. bec_widgets/utils/crosshair.py +13 -9
  11. bec_widgets/utils/ui_loader.py +58 -0
  12. bec_widgets/widgets/__init__.py +1 -0
  13. bec_widgets/widgets/motor_control/motor_table/motor_table.py +44 -43
  14. bec_widgets/widgets/motor_control/movement_absolute/movement_absolute.py +25 -23
  15. bec_widgets/widgets/motor_control/movement_relative/movement_relative.py +51 -48
  16. bec_widgets/widgets/spiral_progress_bar/__init__.py +1 -0
  17. bec_widgets/widgets/spiral_progress_bar/ring.py +184 -0
  18. bec_widgets/widgets/spiral_progress_bar/spiral_progress_bar.py +594 -0
  19. {bec_widgets-0.54.0.dist-info → bec_widgets-0.56.0.dist-info}/METADATA +3 -1
  20. {bec_widgets-0.54.0.dist-info → bec_widgets-0.56.0.dist-info}/RECORD +29 -46
  21. docs/user/apps.md +1 -26
  22. pyproject.toml +2 -1
  23. tests/end-2-end/test_bec_dock_rpc_e2e.py +81 -0
  24. tests/unit_tests/test_client_utils.py +2 -2
  25. tests/unit_tests/test_crosshair.py +5 -5
  26. tests/unit_tests/test_motor_control.py +49 -45
  27. tests/unit_tests/test_spiral_progress_bar.py +338 -0
  28. bec_widgets/examples/eiger_plot/__init__.py +0 -0
  29. bec_widgets/examples/eiger_plot/eiger_plot.py +0 -307
  30. bec_widgets/examples/eiger_plot/eiger_plot.ui +0 -207
  31. bec_widgets/examples/mca_readout/__init__.py +0 -0
  32. bec_widgets/examples/mca_readout/mca_plot.py +0 -159
  33. bec_widgets/examples/mca_readout/mca_sim.py +0 -28
  34. bec_widgets/examples/modular_app/___init__.py +0 -0
  35. bec_widgets/examples/modular_app/modular.ui +0 -92
  36. bec_widgets/examples/modular_app/modular_app.py +0 -197
  37. bec_widgets/examples/motor_movement/config_example.yaml +0 -17
  38. bec_widgets/examples/motor_movement/csax_bec_config.yaml +0 -10
  39. bec_widgets/examples/motor_movement/csaxs_config.yaml +0 -17
  40. bec_widgets/examples/motor_movement/motor_example.py +0 -1344
  41. bec_widgets/examples/stream_plot/__init__.py +0 -0
  42. bec_widgets/examples/stream_plot/line_plot.ui +0 -155
  43. bec_widgets/examples/stream_plot/stream_plot.py +0 -337
  44. docs/user/apps/modular_app.md +0 -6
  45. docs/user/apps/motor_app.md +0 -34
  46. docs/user/apps/motor_app_10fps.gif +0 -0
  47. docs/user/apps/plot_app.md +0 -6
  48. tests/unit_tests/test_eiger_plot.py +0 -115
  49. tests/unit_tests/test_stream_plot.py +0 -158
  50. {bec_widgets-0.54.0.dist-info → bec_widgets-0.56.0.dist-info}/WHEEL +0 -0
  51. {bec_widgets-0.54.0.dist-info → bec_widgets-0.56.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,594 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal, Optional
4
+
5
+ import pyqtgraph as pg
6
+ from bec_lib.endpoints import MessageEndpoints
7
+ from pydantic import Field, field_validator
8
+ from pydantic_core import PydanticCustomError
9
+ from qtpy import QtCore, QtGui
10
+ from qtpy.QtCore import QSize, Slot
11
+ from qtpy.QtWidgets import QSizePolicy, QWidget
12
+
13
+ from bec_widgets.utils import BECConnector, Colors, ConnectionConfig, EntryValidator
14
+ from bec_widgets.widgets.spiral_progress_bar.ring import Ring, RingConfig
15
+
16
+
17
+ class SpiralProgressBarConfig(ConnectionConfig):
18
+ color_map: str | None = Field("magma", description="Color scheme for the progress bars.")
19
+ min_number_of_bars: int | None = Field(
20
+ 1, description="Minimum number of progress bars to display."
21
+ )
22
+ max_number_of_bars: int | None = Field(
23
+ 10, description="Maximum number of progress bars to display."
24
+ )
25
+ num_bars: int | None = Field(1, description="Number of progress bars to display.")
26
+ gap: int | None = Field(10, description="Gap between progress bars.")
27
+ auto_updates: bool | None = Field(
28
+ True, description="Enable or disable updates based on scan queue status."
29
+ )
30
+ rings: list[RingConfig] | None = Field([], description="List of ring configurations.")
31
+
32
+ @field_validator("num_bars")
33
+ def validate_num_bars(cls, v, values):
34
+ min_number_of_bars = values.data.get("min_number_of_bars", None)
35
+ max_number_of_bars = values.data.get("max_number_of_bars", None)
36
+ if min_number_of_bars is not None and max_number_of_bars is not None:
37
+ print(
38
+ f"Number of bars adjusted to be between defined min:{min_number_of_bars} and max:{max_number_of_bars} number of bars."
39
+ )
40
+ v = max(min_number_of_bars, min(v, max_number_of_bars))
41
+ return v
42
+
43
+ @field_validator("rings")
44
+ def validate_rings(cls, v, values):
45
+ if v is not None and v is not []:
46
+ num_bars = values.data.get("num_bars", None)
47
+ if len(v) != num_bars:
48
+ raise PydanticCustomError(
49
+ "different number of configs",
50
+ f"Length of rings configuration ({len(v)}) does not match the number of bars ({num_bars}).",
51
+ {"wrong_value": len(v)},
52
+ )
53
+ indices = [ring.index for ring in v]
54
+ if sorted(indices) != list(range(len(indices))):
55
+ raise PydanticCustomError(
56
+ "wrong indices",
57
+ f"Indices of ring configurations must be unique and in order from 0 to num_bars {num_bars}.",
58
+ {"wrong_value": indices},
59
+ )
60
+ return v
61
+
62
+ @field_validator("color_map")
63
+ def validate_color_map(cls, v, values):
64
+ if v is not None and v != "":
65
+ if v not in pg.colormap.listMaps():
66
+ raise PydanticCustomError(
67
+ "unsupported colormap",
68
+ f"Colormap '{v}' not found in the current installation of pyqtgraph",
69
+ {"wrong_value": v},
70
+ )
71
+ return v
72
+
73
+
74
+ class SpiralProgressBar(BECConnector, QWidget):
75
+ USER_ACCESS = [
76
+ "get_all_rpc",
77
+ "rpc_id",
78
+ "config_dict",
79
+ "rings",
80
+ "update_config",
81
+ "add_ring",
82
+ "remove_ring",
83
+ "set_precision",
84
+ "set_min_max_values",
85
+ "set_number_of_bars",
86
+ "set_value",
87
+ "set_colors_from_map",
88
+ "set_colors_directly",
89
+ "set_line_widths",
90
+ "set_gap",
91
+ "set_diameter",
92
+ "reset_diameter",
93
+ "enable_auto_updates",
94
+ ]
95
+
96
+ def __init__(
97
+ self,
98
+ parent=None,
99
+ config: SpiralProgressBarConfig | dict | None = None,
100
+ client=None,
101
+ gui_id: str | None = None,
102
+ num_bars: int | None = None,
103
+ ):
104
+ if config is None:
105
+ config = SpiralProgressBarConfig(widget_class=self.__class__.__name__)
106
+ self.config = config
107
+ else:
108
+ if isinstance(config, dict):
109
+ config = SpiralProgressBarConfig(**config, widget_class=self.__class__.__name__)
110
+ self.config = config
111
+ super().__init__(client=client, config=config, gui_id=gui_id)
112
+ QWidget.__init__(self, parent=None)
113
+
114
+ self.get_bec_shortcuts()
115
+ self.entry_validator = EntryValidator(self.dev)
116
+
117
+ self.RID = None
118
+ self.values = None
119
+
120
+ # For updating bar behaviour
121
+ self._auto_updates = True
122
+ self._rings = []
123
+
124
+ if num_bars is not None:
125
+ self.config.num_bars = max(
126
+ self.config.min_number_of_bars, min(num_bars, self.config.max_number_of_bars)
127
+ )
128
+ self.initialize_bars()
129
+
130
+ self.enable_auto_updates(self.config.auto_updates)
131
+
132
+ @property
133
+ def rings(self):
134
+ return self._rings
135
+
136
+ @rings.setter
137
+ def rings(self, value):
138
+ self._rings = value
139
+
140
+ def update_config(self, config: SpiralProgressBarConfig | dict):
141
+ """
142
+ Update the configuration of the widget.
143
+
144
+ Args:
145
+ config(SpiralProgressBarConfig|dict): Configuration to update.
146
+ """
147
+ if isinstance(config, dict):
148
+ config = SpiralProgressBarConfig(**config, widget_class=self.__class__.__name__)
149
+ self.config = config
150
+ self.clear_all()
151
+
152
+ def initialize_bars(self):
153
+ """
154
+ Initialize the progress bars.
155
+ """
156
+ start_positions = [90 * 16] * self.config.num_bars
157
+ directions = [-1] * self.config.num_bars
158
+
159
+ self.config.rings = [
160
+ RingConfig(
161
+ widget_class="Ring",
162
+ index=i,
163
+ start_positions=start_positions[i],
164
+ directions=directions[i],
165
+ )
166
+ for i in range(self.config.num_bars)
167
+ ]
168
+ self._rings = [
169
+ Ring(parent_progress_widget=self, config=config) for config in self.config.rings
170
+ ]
171
+
172
+ if self.config.color_map:
173
+ self.set_colors_from_map(self.config.color_map)
174
+
175
+ min_size = self._calculate_minimum_size()
176
+ self.setMinimumSize(min_size)
177
+ self.update()
178
+
179
+ def add_ring(self, **kwargs) -> Ring:
180
+ """
181
+ Add a new progress bar.
182
+
183
+ Args:
184
+ **kwargs: Keyword arguments for the new progress bar.
185
+
186
+ Returns:
187
+ Ring: Ring object.
188
+ """
189
+ if self.config.num_bars < self.config.max_number_of_bars:
190
+ ring = Ring(parent_progress_widget=self, **kwargs)
191
+ ring.config.index = self.config.num_bars
192
+ self.config.num_bars += 1
193
+ self._rings.append(ring)
194
+ self.config.rings.append(ring.config)
195
+ if self.config.color_map:
196
+ self.set_colors_from_map(self.config.color_map)
197
+ self.update()
198
+ return ring
199
+
200
+ def remove_ring(self, index: int):
201
+ """
202
+ Remove a progress bar by index.
203
+
204
+ Args:
205
+ index(int): Index of the progress bar to remove.
206
+ """
207
+ ring = self._find_ring_by_index(index)
208
+ ring.cleanup()
209
+ self._rings.remove(ring)
210
+ self.config.rings.remove(ring.config)
211
+ self.config.num_bars -= 1
212
+ self._reindex_rings()
213
+ if self.config.color_map:
214
+ self.set_colors_from_map(self.config.color_map)
215
+ self.update()
216
+
217
+ def _reindex_rings(self):
218
+ """
219
+ Reindex the progress bars.
220
+ """
221
+ for i, ring in enumerate(self._rings):
222
+ ring.config.index = i
223
+
224
+ def set_precision(self, precision: int, bar_index: int = None):
225
+ """
226
+ Set the precision for the progress bars. If bar_index is not provide, the precision will be set for all progress bars.
227
+
228
+ Args:
229
+ precision(int): Precision for the progress bars.
230
+ bar_index(int): Index of the progress bar to set the precision for. If provided, only a single precision can be set.
231
+ """
232
+ if bar_index is not None:
233
+ bar_index = self._bar_index_check(bar_index)
234
+ ring = self._find_ring_by_index(bar_index)
235
+ ring.config.precision = precision
236
+ else:
237
+ for ring in self._rings:
238
+ ring.config.precision = precision
239
+ self.update()
240
+
241
+ def set_min_max_values(
242
+ self,
243
+ min_values: int | float | list[int | float],
244
+ max_values: int | float | list[int | float],
245
+ ):
246
+ """
247
+ Set the minimum and maximum values for the progress bars.
248
+
249
+ Args:
250
+ min_values(int|float | list[float]): Minimum value(s) for the progress bars. If multiple progress bars are displayed, provide a list of minimum values for each progress bar.
251
+ max_values(int|float | list[float]): Maximum value(s) for the progress bars. If multiple progress bars are displayed, provide a list of maximum values for each progress bar.
252
+ """
253
+ if isinstance(min_values, int) or isinstance(min_values, float):
254
+ min_values = [min_values]
255
+ if isinstance(max_values, int) or isinstance(max_values, float):
256
+ max_values = [max_values]
257
+ min_values = self._adjust_list_to_bars(min_values)
258
+ max_values = self._adjust_list_to_bars(max_values)
259
+ for ring, min_value, max_value in zip(self._rings, min_values, max_values):
260
+ ring.set_min_max_values(min_value, max_value)
261
+ self.update()
262
+
263
+ def set_number_of_bars(self, num_bars: int):
264
+ """
265
+ Set the number of progress bars to display.
266
+
267
+ Args:
268
+ num_bars(int): Number of progress bars to display.
269
+ """
270
+ num_bars = max(
271
+ self.config.min_number_of_bars, min(num_bars, self.config.max_number_of_bars)
272
+ )
273
+ if num_bars != self.config.num_bars:
274
+ self.config.num_bars = num_bars
275
+ self.initialize_bars()
276
+
277
+ def set_value(self, values: int | list, ring_index: int = None):
278
+ """
279
+ Set the values for the progress bars.
280
+
281
+ Args:
282
+ values(int | tuple): Value(s) for the progress bars. If multiple progress bars are displayed, provide a tuple of values for each progress bar.
283
+ ring_index(int): Index of the progress bar to set the value for. If provided, only a single value can be set.
284
+
285
+ Examples:
286
+ >>> SpiralProgressBar.set_value(50)
287
+ >>> SpiralProgressBar.set_value([30, 40, 50]) # (outer, middle, inner)
288
+ >>> SpiralProgressBar.set_value(60, bar_index=1) # Set the value for the middle progress bar.
289
+ """
290
+ if ring_index is not None:
291
+ ring = self._find_ring_by_index(ring_index)
292
+ if isinstance(values, list):
293
+ values = values[0]
294
+ print(
295
+ f"Warning: Only a single value can be set for a single progress bar. Using the first value in the list {values}"
296
+ )
297
+ ring.set_value(values)
298
+ else:
299
+ if isinstance(values, int):
300
+ values = [values]
301
+ values = self._adjust_list_to_bars(values)
302
+ for ring, value in zip(self._rings, values):
303
+ ring.set_value(value)
304
+ self.update()
305
+
306
+ def set_colors_from_map(self, colormap, color_format: Literal["RGB", "HEX"] = "RGB"):
307
+ """
308
+ Set the colors for the progress bars from a colormap.
309
+
310
+ Args:
311
+ colormap(str): Name of the colormap.
312
+ color_format(Literal["RGB","HEX"]): Format of the returned colors ('RGB', 'HEX').
313
+ """
314
+ if colormap not in pg.colormap.listMaps():
315
+ raise ValueError(
316
+ f"Colormap '{colormap}' not found in the current installation of pyqtgraph"
317
+ )
318
+ colors = Colors.golden_angle_color(colormap, self.config.num_bars, color_format)
319
+ self.set_colors_directly(colors)
320
+ self.config.color_map = colormap
321
+ self.update()
322
+
323
+ def set_colors_directly(self, colors: list[str | tuple] | str | tuple, bar_index: int = None):
324
+ """
325
+ Set the colors for the progress bars directly.
326
+
327
+ Args:
328
+ colors(list[str | tuple] | str | tuple): Color(s) for the progress bars. If multiple progress bars are displayed, provide a list of colors for each progress bar.
329
+ bar_index(int): Index of the progress bar to set the color for. If provided, only a single color can be set.
330
+ """
331
+ if bar_index is not None and isinstance(colors, (str, tuple)):
332
+ bar_index = self._bar_index_check(bar_index)
333
+ ring = self._find_ring_by_index(bar_index)
334
+ ring.set_color(colors)
335
+ else:
336
+ if isinstance(colors, (str, tuple)):
337
+ colors = [colors]
338
+ colors = self._adjust_list_to_bars(colors)
339
+ for ring, color in zip(self._rings, colors):
340
+ ring.set_color(color)
341
+ self.config.color_map = None
342
+ self.update()
343
+
344
+ def set_line_widths(self, widths: int | list[int], bar_index: int = None):
345
+ """
346
+ Set the line widths for the progress bars.
347
+
348
+ Args:
349
+ widths(int | list[int]): Line width(s) for the progress bars. If multiple progress bars are displayed, provide a list of line widths for each progress bar.
350
+ bar_index(int): Index of the progress bar to set the line width for. If provided, only a single line width can be set.
351
+ """
352
+ if bar_index is not None:
353
+ bar_index = self._bar_index_check(bar_index)
354
+ ring = self._find_ring_by_index(bar_index)
355
+ if isinstance(widths, list):
356
+ widths = widths[0]
357
+ print(
358
+ f"Warning: Only a single line width can be set for a single progress bar. Using the first value in the list {widths}"
359
+ )
360
+ ring.set_line_width(widths)
361
+ else:
362
+ if isinstance(widths, int):
363
+ widths = [widths]
364
+ widths = self._adjust_list_to_bars(widths)
365
+ self.config.gap = max(widths) * 2
366
+ for ring, width in zip(self._rings, widths):
367
+ ring.set_line_width(width)
368
+ min_size = self._calculate_minimum_size()
369
+ self.setMinimumSize(min_size)
370
+ self.update()
371
+
372
+ def set_gap(self, gap: int):
373
+ """
374
+ Set the gap between the progress bars.
375
+
376
+ Args:
377
+ gap(int): Gap between the progress bars.
378
+ """
379
+ self.config.gap = gap
380
+ self.update()
381
+
382
+ def set_diameter(self, diameter: int):
383
+ """
384
+ Set the diameter of the widget.
385
+
386
+ Args:
387
+ diameter(int): Diameter of the widget.
388
+ """
389
+ size = QSize(diameter, diameter)
390
+ self.resize(size)
391
+ self.setFixedSize(size)
392
+
393
+ def _find_ring_by_index(self, index: int) -> Ring:
394
+ """
395
+ Find the ring by index.
396
+
397
+ Args:
398
+ index(int): Index of the ring.
399
+
400
+ Returns:
401
+ Ring: Ring object.
402
+ """
403
+ found_ring = None
404
+ for ring in self._rings:
405
+ if ring.config.index == index:
406
+ found_ring = ring
407
+ break
408
+ if found_ring is None:
409
+ raise ValueError(f"Ring with index {index} not found.")
410
+ return found_ring
411
+
412
+ def enable_auto_updates(self, enable: bool = True):
413
+ """
414
+ Enable or disable updates based on scan status. Overrides manual updates.
415
+ The behaviour of the whole progress bar widget will be driven by the scan queue status.
416
+
417
+ Args:
418
+ enable(bool): True or False.
419
+
420
+ Returns:
421
+ bool: True if scan segment updates are enabled.
422
+ """
423
+
424
+ self._auto_updates = enable
425
+ if enable is True:
426
+ self.bec_dispatcher.connect_slot(
427
+ self.on_scan_queue_status, MessageEndpoints.scan_queue_status()
428
+ )
429
+ else:
430
+ self.bec_dispatcher.disconnect_slot(
431
+ self.on_scan_queue_status, MessageEndpoints.scan_queue_status()
432
+ )
433
+ return self._auto_updates
434
+
435
+ @Slot(dict, dict)
436
+ def on_scan_queue_status(self, msg, meta):
437
+ primary_queue = msg.get("queue").get("primary")
438
+ info = primary_queue.get("info", None)
439
+
440
+ if info:
441
+ active_request_block = info[0].get("active_request_block", None)
442
+ if active_request_block:
443
+ report_instructions = active_request_block.get("report_instructions", None)
444
+ if report_instructions:
445
+ instruction_type = list(report_instructions[0].keys())[0]
446
+ if instruction_type == "scan_progress":
447
+ if self.config.num_bars != 1:
448
+ self.set_number_of_bars(1)
449
+ self._hook_scan_progress(ring_index=0)
450
+ elif instruction_type == "readback":
451
+ devices = report_instructions[0].get("readback").get("devices")
452
+ start = report_instructions[0].get("readback").get("start")
453
+ end = report_instructions[0].get("readback").get("end")
454
+ if self.config.num_bars != len(devices):
455
+ self.set_number_of_bars(len(devices))
456
+ for index, device in enumerate(devices):
457
+ self._hook_readback(index, device, start[index], end[index])
458
+ else:
459
+ print(f"{instruction_type} not supported yet.")
460
+
461
+ # elif instruction_type == "device_progress":
462
+ # print("hook device_progress")
463
+
464
+ def _hook_scan_progress(self, ring_index: int = None):
465
+ if ring_index is not None:
466
+ ring = self._find_ring_by_index(ring_index)
467
+ else:
468
+ ring = self._rings[0]
469
+
470
+ if ring.config.connections.slot == "on_scan_progress":
471
+ return
472
+ else:
473
+ ring.set_connections("on_scan_progress", MessageEndpoints.scan_progress())
474
+
475
+ def _hook_readback(self, bar_index: int, device: str, min: float | int, max: float | int):
476
+ ring = self._find_ring_by_index(bar_index)
477
+ ring.set_min_max_values(min, max)
478
+ endpoint = MessageEndpoints.device_readback(device)
479
+ ring.set_connections("on_device_readback", endpoint)
480
+
481
+ def _adjust_list_to_bars(self, items: list) -> list:
482
+ """
483
+ Utility method to adjust the list of parameters to match the number of progress bars.
484
+
485
+ Args:
486
+ items(list): List of parameters for the progress bars.
487
+
488
+ Returns:
489
+ list: List of parameters for the progress bars.
490
+ """
491
+ if items is None:
492
+ raise ValueError(
493
+ "Items cannot be None. Please provide a list for parameters for the progress bars."
494
+ )
495
+ if not isinstance(items, list):
496
+ items = [items]
497
+ if len(items) < self.config.num_bars:
498
+ last_item = items[-1]
499
+ items.extend([last_item] * (self.config.num_bars - len(items)))
500
+ elif len(items) > self.config.num_bars:
501
+ items = items[: self.config.num_bars]
502
+ return items
503
+
504
+ def _bar_index_check(self, bar_index: int):
505
+ """
506
+ Utility method to check if the bar index is within the range of the number of progress bars.
507
+
508
+ Args:
509
+ bar_index(int): Index of the progress bar to set the value for.
510
+ """
511
+ if not (0 <= bar_index < self.config.num_bars):
512
+ raise ValueError(
513
+ f"bar_index {bar_index} out of range of number of bars {self.config.num_bars}."
514
+ )
515
+ return bar_index
516
+
517
+ def paintEvent(self, event):
518
+ painter = QtGui.QPainter(self)
519
+ painter.setRenderHint(QtGui.QPainter.Antialiasing)
520
+ size = min(self.width(), self.height())
521
+ rect = QtCore.QRect(0, 0, size, size)
522
+ rect.adjust(
523
+ max(ring.config.line_width for ring in self._rings),
524
+ max(ring.config.line_width for ring in self._rings),
525
+ -max(ring.config.line_width for ring in self._rings),
526
+ -max(ring.config.line_width for ring in self._rings),
527
+ )
528
+
529
+ for i, ring in enumerate(self._rings):
530
+ # Background arc
531
+ painter.setPen(
532
+ QtGui.QPen(ring.background_color, ring.config.line_width, QtCore.Qt.SolidLine)
533
+ )
534
+ offset = self.config.gap * i
535
+ adjusted_rect = QtCore.QRect(
536
+ rect.left() + offset,
537
+ rect.top() + offset,
538
+ rect.width() - 2 * offset,
539
+ rect.height() - 2 * offset,
540
+ )
541
+ painter.drawArc(adjusted_rect, ring.config.start_position, 360 * 16)
542
+
543
+ # Foreground arc
544
+ pen = QtGui.QPen(ring.color, ring.config.line_width, QtCore.Qt.SolidLine)
545
+ pen.setCapStyle(QtCore.Qt.RoundCap)
546
+ painter.setPen(pen)
547
+ proportion = (ring.value - ring.config.min_value) / (
548
+ (ring.config.max_value - ring.config.min_value) + 1e-3
549
+ )
550
+ angle = int(proportion * 360 * 16 * ring.config.direction)
551
+ painter.drawArc(adjusted_rect, ring.start_position, angle)
552
+
553
+ def reset_diameter(self):
554
+ """
555
+ Reset the fixed size of the widget.
556
+ """
557
+ self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
558
+ self.setMinimumSize(self._calculate_minimum_size())
559
+ self.setMaximumSize(16777215, 16777215)
560
+
561
+ def _calculate_minimum_size(self):
562
+ """
563
+ Calculate the minimum size of the widget.
564
+ """
565
+ if not self.config.rings:
566
+ print("no rings to get size from setting size to 10x10")
567
+ return QSize(10, 10)
568
+ ring_widths = [self.config.rings[i].line_width for i in range(self.config.num_bars)]
569
+ total_width = sum(ring_widths) + self.config.gap * (self.config.num_bars - 1)
570
+ diameter = total_width * 2
571
+ if diameter < 50:
572
+ diameter = 50
573
+ return QSize(diameter, diameter)
574
+
575
+ def sizeHint(self):
576
+ min_size = self._calculate_minimum_size()
577
+ return min_size
578
+
579
+ def clear_all(self):
580
+ for ring in self._rings:
581
+ ring.cleanup()
582
+ del ring
583
+ self._rings = []
584
+ self.update()
585
+ self.initialize_bars()
586
+
587
+ def cleanup(self):
588
+ self.bec_dispatcher.disconnect_slot(
589
+ self.on_scan_queue_status, MessageEndpoints.scan_queue_status()
590
+ )
591
+ for ring in self._rings:
592
+ ring.cleanup()
593
+ del ring
594
+ super().cleanup()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bec_widgets
3
- Version: 0.54.0
3
+ Version: 0.56.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
@@ -31,3 +31,5 @@ Provides-Extra: pyqt5
31
31
  Requires-Dist: pyqt5>=5.9; extra == 'pyqt5'
32
32
  Provides-Extra: pyqt6
33
33
  Requires-Dist: pyqt6>=6.7; extra == 'pyqt6'
34
+ Provides-Extra: pyside6
35
+ Requires-Dist: pyside6>=6.7; extra == 'pyside6'