bec-widgets 2.16.1__py3-none-any.whl → 2.17.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.
@@ -1,12 +1,46 @@
1
1
  import sys
2
+ from enum import Enum
2
3
  from string import Template
3
4
 
4
- from qtpy.QtCore import Property, QEasingCurve, QPropertyAnimation, QRectF, Qt, QTimer, Slot
5
+ from qtpy.QtCore import QEasingCurve, QPropertyAnimation, QRectF, Qt, QTimer
5
6
  from qtpy.QtGui import QColor, QPainter, QPainterPath
7
+
8
+
9
+ class ProgressState(Enum):
10
+ NORMAL = "normal"
11
+ PAUSED = "paused"
12
+ INTERRUPTED = "interrupted"
13
+ COMPLETED = "completed"
14
+
15
+ @classmethod
16
+ def from_bec_status(cls, status: str) -> "ProgressState":
17
+ """
18
+ Map a BEC status string (open, paused, aborted, halted, closed)
19
+ to the corresponding ProgressState.
20
+ Any unknown status falls back to NORMAL.
21
+ """
22
+ mapping = {
23
+ "open": cls.NORMAL,
24
+ "paused": cls.PAUSED,
25
+ "aborted": cls.INTERRUPTED,
26
+ "halted": cls.PAUSED,
27
+ "closed": cls.COMPLETED,
28
+ }
29
+ return mapping.get(status.lower(), cls.NORMAL)
30
+
31
+
32
+ PROGRESS_STATE_COLORS = {
33
+ ProgressState.NORMAL: QColor("#2979ff"), # blue – normal progress
34
+ ProgressState.PAUSED: QColor("#ffca28"), # orange/amber – paused
35
+ ProgressState.INTERRUPTED: QColor("#ff5252"), # red – interrupted
36
+ ProgressState.COMPLETED: QColor("#00e676"), # green – finished
37
+ }
38
+
6
39
  from qtpy.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget
7
40
 
8
41
  from bec_widgets.utils.bec_widget import BECWidget
9
42
  from bec_widgets.utils.colors import get_accent_colors
43
+ from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
10
44
 
11
45
 
12
46
  class BECProgressBar(BECWidget, QWidget):
@@ -21,6 +55,8 @@ class BECProgressBar(BECWidget, QWidget):
21
55
  "set_minimum",
22
56
  "label_template",
23
57
  "label_template.setter",
58
+ "state",
59
+ "state.setter",
24
60
  "_get_label",
25
61
  ]
26
62
  ICON_NAME = "page_control"
@@ -48,27 +84,38 @@ class BECProgressBar(BECWidget, QWidget):
48
84
 
49
85
  self._completed_color = accent_colors.success
50
86
  self._border_color = QColor(50, 50, 50)
87
+ # Corner‑rounding: base radius in pixels (auto‑reduced if bar is small)
88
+ self._corner_radius = 10
89
+
90
+ # Progress‑bar state handling
91
+ self._state = ProgressState.NORMAL
92
+ self._state_colors = dict(PROGRESS_STATE_COLORS)
51
93
 
52
94
  # layout settings
95
+ self._padding_left_right = 10
53
96
  self._value_animation = QPropertyAnimation(self, b"_progressbar_value")
54
97
  self._value_animation.setDuration(200)
55
98
  self._value_animation.setEasingCurve(QEasingCurve.Type.OutCubic)
56
99
 
57
100
  # label on top of the progress bar
58
101
  self.center_label = QLabel(self)
59
- self.center_label.setAlignment(Qt.AlignCenter)
102
+ self.center_label.setAlignment(Qt.AlignHCenter)
60
103
  self.center_label.setStyleSheet("color: white;")
61
104
  self.center_label.setMinimumSize(0, 0)
62
105
 
63
106
  layout = QVBoxLayout(self)
64
- layout.setContentsMargins(0, 0, 0, 0)
107
+ layout.setContentsMargins(10, 0, 10, 0)
65
108
  layout.setSpacing(0)
66
109
  layout.addWidget(self.center_label)
110
+ layout.setAlignment(self.center_label, Qt.AlignCenter)
67
111
  self.setLayout(layout)
68
112
 
69
113
  self.update()
114
+ self._adjust_label_width()
70
115
 
71
- @Property(str, doc="The template for the center label. Use $value, $maximum, and $percentage.")
116
+ @SafeProperty(
117
+ str, doc="The template for the center label. Use $value, $maximum, and $percentage."
118
+ )
72
119
  def label_template(self):
73
120
  """
74
121
  The template for the center label. Use $value, $maximum, and $percentage to insert the values.
@@ -83,10 +130,11 @@ class BECProgressBar(BECWidget, QWidget):
83
130
  @label_template.setter
84
131
  def label_template(self, template):
85
132
  self._label_template = template
133
+ self._adjust_label_width()
86
134
  self.set_value(self._user_value)
87
135
  self.update()
88
136
 
89
- @Property(float, designable=False)
137
+ @SafeProperty(float, designable=False)
90
138
  def _progressbar_value(self):
91
139
  """
92
140
  The current value of the progress bar.
@@ -106,8 +154,20 @@ class BECProgressBar(BECWidget, QWidget):
106
154
  percentage=int((self.map_value(self._user_value) / self._maximum) * 100),
107
155
  )
108
156
 
109
- @Slot(float)
110
- @Slot(int)
157
+ def _adjust_label_width(self):
158
+ """
159
+ Reserve enough horizontal space for the center label so the widget
160
+ doesn't resize as the text grows during progress.
161
+ """
162
+ template = Template(self._label_template)
163
+ sample_text = template.safe_substitute(
164
+ value=self._user_maximum, maximum=self._user_maximum, percentage=100
165
+ )
166
+ width = self.center_label.fontMetrics().horizontalAdvance(sample_text)
167
+ self.center_label.setFixedWidth(width)
168
+
169
+ @SafeSlot(float)
170
+ @SafeSlot(int)
111
171
  def set_value(self, value):
112
172
  """
113
173
  Set the value of the progress bar.
@@ -122,35 +182,88 @@ class BECProgressBar(BECWidget, QWidget):
122
182
  self._target_value = self.map_value(value)
123
183
  self._user_value = value
124
184
  self.center_label.setText(self._update_template())
185
+ # Update state automatically unless paused or interrupted
186
+ if self._state not in (ProgressState.PAUSED, ProgressState.INTERRUPTED):
187
+ self._state = (
188
+ ProgressState.COMPLETED
189
+ if self._user_value >= self._user_maximum
190
+ else ProgressState.NORMAL
191
+ )
125
192
  self.animate_progress()
126
193
 
194
+ @SafeProperty(object, doc="Current visual state of the progress bar.")
195
+ def state(self):
196
+ return self._state
197
+
198
+ @state.setter
199
+ def state(self, state):
200
+ """
201
+ Set the visual state of the progress bar.
202
+
203
+ Args:
204
+ state(ProgressState | str): The state to set. Can be one of the
205
+ """
206
+ if isinstance(state, str):
207
+ state = ProgressState(state.lower())
208
+ if not isinstance(state, ProgressState):
209
+ raise ValueError("state must be a ProgressState or its value")
210
+ self._state = state
211
+ self.update()
212
+
213
+ @SafeProperty(float, doc="Base corner radius in pixels (auto‑scaled down on small bars).")
214
+ def corner_radius(self) -> float:
215
+ return self._corner_radius
216
+
217
+ @corner_radius.setter
218
+ def corner_radius(self, radius: float):
219
+ self._corner_radius = max(0.0, radius)
220
+ self.update()
221
+
222
+ @SafeProperty(float)
223
+ def padding_left_right(self) -> float:
224
+ return self._padding_left_right
225
+
226
+ @padding_left_right.setter
227
+ def padding_left_right(self, padding: float):
228
+ self._padding_left_right = padding
229
+ self.update()
230
+
127
231
  def paintEvent(self, event):
128
232
  painter = QPainter(self)
129
233
  painter.setRenderHint(QPainter.Antialiasing)
130
- rect = self.rect().adjusted(10, 0, -10, -1)
234
+ rect = self.rect().adjusted(self._padding_left_right, 0, -self._padding_left_right, -1)
235
+
236
+ # Corner radius adapts to widget height so it never exceeds half the bar’s thickness
237
+ radius = min(self._corner_radius, rect.height() / 2)
131
238
 
132
239
  # Draw background
133
240
  painter.setBrush(self._background_color)
134
241
  painter.setPen(Qt.NoPen)
135
- painter.drawRoundedRect(rect, 10, 10) # Rounded corners
242
+ painter.drawRoundedRect(rect, radius, radius) # Rounded corners
136
243
 
137
244
  # Draw border
138
245
  painter.setBrush(Qt.NoBrush)
139
246
  painter.setPen(self._border_color)
140
- painter.drawRoundedRect(rect, 10, 10)
141
-
142
- # Determine progress color based on completion
143
- if self._value >= self._maximum:
144
- current_color = self._completed_color
247
+ painter.drawRoundedRect(rect, radius, radius)
248
+
249
+ # Determine progress colour based on current state
250
+ if self._state == ProgressState.PAUSED:
251
+ current_color = self._state_colors[ProgressState.PAUSED]
252
+ elif self._state == ProgressState.INTERRUPTED:
253
+ current_color = self._state_colors[ProgressState.INTERRUPTED]
254
+ elif self._state == ProgressState.COMPLETED or self._value >= self._maximum:
255
+ current_color = self._state_colors[ProgressState.COMPLETED]
145
256
  else:
146
- current_color = self._progress_color
257
+ current_color = self._state_colors[ProgressState.NORMAL]
147
258
 
148
259
  # Set clipping region to preserve the background's rounded corners
149
260
  progress_rect = rect.adjusted(
150
261
  0, 0, int(-rect.width() + (self._value / self._maximum) * rect.width()), 0
151
262
  )
152
263
  clip_path = QPainterPath()
153
- clip_path.addRoundedRect(QRectF(rect), 10, 10) # Clip to the background's rounded corners
264
+ clip_path.addRoundedRect(
265
+ QRectF(rect), radius, radius
266
+ ) # Clip to the background's rounded corners
154
267
  painter.setClipPath(clip_path)
155
268
 
156
269
  # Draw progress bar
@@ -168,7 +281,7 @@ class BECProgressBar(BECWidget, QWidget):
168
281
  self._value_animation.setEndValue(self._target_value)
169
282
  self._value_animation.start()
170
283
 
171
- @Property(float)
284
+ @SafeProperty(float)
172
285
  def maximum(self):
173
286
  """
174
287
  The maximum value of the progress bar.
@@ -182,7 +295,7 @@ class BECProgressBar(BECWidget, QWidget):
182
295
  """
183
296
  self.set_maximum(maximum)
184
297
 
185
- @Property(float)
298
+ @SafeProperty(float)
186
299
  def minimum(self):
187
300
  """
188
301
  The minimum value of the progress bar.
@@ -193,7 +306,7 @@ class BECProgressBar(BECWidget, QWidget):
193
306
  def minimum(self, minimum: float):
194
307
  self.set_minimum(minimum)
195
308
 
196
- @Property(float)
309
+ @SafeProperty(float)
197
310
  def initial_value(self):
198
311
  """
199
312
  The initial value of the progress bar.
@@ -204,7 +317,7 @@ class BECProgressBar(BECWidget, QWidget):
204
317
  def initial_value(self, value: float):
205
318
  self.set_value(value)
206
319
 
207
- @Slot(float)
320
+ @SafeSlot(float)
208
321
  def set_maximum(self, maximum: float):
209
322
  """
210
323
  Set the maximum value of the progress bar.
@@ -213,10 +326,11 @@ class BECProgressBar(BECWidget, QWidget):
213
326
  maximum (float): The maximum value.
214
327
  """
215
328
  self._user_maximum = maximum
329
+ self._adjust_label_width()
216
330
  self.set_value(self._user_value) # Update the value to fit the new range
217
331
  self.update()
218
332
 
219
- @Slot(float)
333
+ @SafeSlot(float)
220
334
  def set_minimum(self, minimum: float):
221
335
  """
222
336
  Set the minimum value of the progress bar.
@@ -0,0 +1,17 @@
1
+ def main(): # pragma: no cover
2
+ from qtpy import PYSIDE6
3
+
4
+ if not PYSIDE6:
5
+ print("PYSIDE6 is not available in the environment. Cannot patch designer.")
6
+ return
7
+ from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
8
+
9
+ from bec_widgets.widgets.progress.scan_progressbar.scan_progress_bar_plugin import (
10
+ ScanProgressBarPlugin,
11
+ )
12
+
13
+ QPyDesignerCustomWidgetCollection.addCustomWidget(ScanProgressBarPlugin())
14
+
15
+
16
+ if __name__ == "__main__": # pragma: no cover
17
+ main()
@@ -0,0 +1 @@
1
+ {'files': ['scan_progressbar.py']}
@@ -0,0 +1,54 @@
1
+ # Copyright (C) 2022 The Qt Company Ltd.
2
+ # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
3
+
4
+ from qtpy.QtDesigner import QDesignerCustomWidgetInterface
5
+
6
+ from bec_widgets.utils.bec_designer import designer_material_icon
7
+ from bec_widgets.widgets.progress.scan_progressbar.scan_progressbar import ScanProgressBar
8
+
9
+ DOM_XML = """
10
+ <ui language='c++'>
11
+ <widget class='ScanProgressBar' name='scan_progress_bar'>
12
+ </widget>
13
+ </ui>
14
+ """
15
+
16
+
17
+ class ScanProgressBarPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
18
+ def __init__(self):
19
+ super().__init__()
20
+ self._form_editor = None
21
+
22
+ def createWidget(self, parent):
23
+ t = ScanProgressBar(parent)
24
+ return t
25
+
26
+ def domXml(self):
27
+ return DOM_XML
28
+
29
+ def group(self):
30
+ return "BEC Utils"
31
+
32
+ def icon(self):
33
+ return designer_material_icon(ScanProgressBar.ICON_NAME)
34
+
35
+ def includeFile(self):
36
+ return "scan_progress_bar"
37
+
38
+ def initialize(self, form_editor):
39
+ self._form_editor = form_editor
40
+
41
+ def isContainer(self):
42
+ return False
43
+
44
+ def isInitialized(self):
45
+ return self._form_editor is not None
46
+
47
+ def name(self):
48
+ return "ScanProgressBar"
49
+
50
+ def toolTip(self):
51
+ return "A progress bar that is hooked up to the scan progress of a scan."
52
+
53
+ def whatsThis(self):
54
+ return self.toolTip()
@@ -0,0 +1,320 @@
1
+ from __future__ import annotations
2
+
3
+ import enum
4
+ import os
5
+ import time
6
+ from typing import Literal
7
+
8
+ import numpy as np
9
+ from bec_lib.endpoints import MessageEndpoints
10
+ from bec_lib.logger import bec_logger
11
+ from qtpy.QtCore import QObject, QTimer, Signal
12
+ from qtpy.QtWidgets import QVBoxLayout, QWidget
13
+
14
+ from bec_widgets.utils.bec_widget import BECWidget
15
+ from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
16
+ from bec_widgets.utils.ui_loader import UILoader
17
+ from bec_widgets.widgets.progress.bec_progressbar.bec_progressbar import ProgressState
18
+
19
+ logger = bec_logger.logger
20
+
21
+
22
+ class ProgressSource(enum.Enum):
23
+ """
24
+ Enum to define the source of the progress.
25
+ """
26
+
27
+ SCAN_PROGRESS = "scan_progress"
28
+ DEVICE_PROGRESS = "device_progress"
29
+
30
+
31
+ class ProgressTask(QObject):
32
+ """
33
+ Class to store progress information.
34
+ Inspired by https://github.com/Textualize/rich/blob/master/rich/progress.py
35
+ """
36
+
37
+ def __init__(self, parent: QWidget, value: float = 0, max_value: float = 0, done: bool = False):
38
+ super().__init__(parent=parent)
39
+ self.start_time = time.time()
40
+ self.done = done
41
+ self.value = value
42
+ self.max_value = max_value
43
+ self._elapsed_time = 0
44
+
45
+ self.timer = QTimer(self)
46
+ self.timer.timeout.connect(self.update_elapsed_time)
47
+ self.timer.start(100) # update the elapsed time every 100 ms
48
+
49
+ def update(self, value: float, max_value: float, done: bool = False):
50
+ """
51
+ Update the progress.
52
+ """
53
+ self.max_value = max_value
54
+ self.done = done
55
+ self.value = value
56
+ if done:
57
+ self.timer.stop()
58
+
59
+ def update_elapsed_time(self):
60
+ """
61
+ Update the time estimates. This is called every 100 ms by a QTimer.
62
+ """
63
+ self._elapsed_time += 0.1
64
+
65
+ @property
66
+ def percentage(self) -> float:
67
+ """float: Get progress of task as a percentage. If a None total was set, returns 0"""
68
+ if not self.max_value:
69
+ return 0.0
70
+ completed = (self.value / self.max_value) * 100.0
71
+ completed = min(100.0, max(0.0, completed))
72
+ return completed
73
+
74
+ @property
75
+ def speed(self) -> float:
76
+ """Get the estimated speed in steps per second."""
77
+ if self._elapsed_time == 0:
78
+ return 0.0
79
+
80
+ return self.value / self._elapsed_time
81
+
82
+ @property
83
+ def frequency(self) -> float:
84
+ """Get the estimated frequency in steps per second."""
85
+ if self.speed == 0:
86
+ return 0.0
87
+ return 1 / self.speed
88
+
89
+ @property
90
+ def time_elapsed(self) -> str:
91
+ # format the elapsed time to a string in the format HH:MM:SS
92
+ return self._format_time(int(self._elapsed_time))
93
+
94
+ @property
95
+ def remaining(self) -> float:
96
+ """Get the estimated remaining steps."""
97
+ if self.done:
98
+ return 0.0
99
+ remaining = self.max_value - self.value
100
+ return remaining
101
+
102
+ @property
103
+ def time_remaining(self) -> str:
104
+ """
105
+ Get the estimated remaining time in the format HH:MM:SS.
106
+ """
107
+ if self.done or not self.speed or not self.remaining:
108
+ return self._format_time(0)
109
+ estimate = int(np.round(self.remaining / self.speed))
110
+
111
+ return self._format_time(estimate)
112
+
113
+ def _format_time(self, seconds: float) -> str:
114
+ """
115
+ Format the time in seconds to a string in the format HH:MM:SS.
116
+ """
117
+ return f"{seconds // 3600:02}:{(seconds // 60) % 60:02}:{seconds % 60:02}"
118
+
119
+
120
+ class ScanProgressBar(BECWidget, QWidget):
121
+ """
122
+ Widget to display a progress bar that is hooked up to the scan progress of a scan.
123
+ If you want to manually set the progress, it is recommended to use the BECProgressbar or QProgressbar directly.
124
+ """
125
+
126
+ ICON_NAME = "timelapse"
127
+ progress_started = Signal()
128
+ progress_finished = Signal()
129
+
130
+ def __init__(self, parent=None, client=None, config=None, gui_id=None, one_line_design=False):
131
+ super().__init__(parent=parent, client=client, config=config, gui_id=gui_id)
132
+
133
+ self.get_bec_shortcuts()
134
+ ui_file = os.path.join(
135
+ os.path.dirname(__file__),
136
+ "scan_progressbar_one_line.ui" if one_line_design else "scan_progressbar.ui",
137
+ )
138
+ self.ui = UILoader(self).loader(ui_file)
139
+ self.layout = QVBoxLayout(self)
140
+ self.layout.setSpacing(0)
141
+ self.layout.setContentsMargins(0, 0, 0, 0)
142
+ self.layout.addWidget(self.ui)
143
+ self.setLayout(self.layout)
144
+ self.progressbar = self.ui.progressbar
145
+
146
+ self.connect_to_queue()
147
+ self._progress_source = None
148
+ self.task = None
149
+ self.scan_number = None
150
+ self.progress_started.connect(lambda: print("Scan progress started"))
151
+
152
+ def connect_to_queue(self):
153
+ """
154
+ Connect to the queue status signal.
155
+ """
156
+ self.bec_dispatcher.connect_slot(self.on_queue_update, MessageEndpoints.scan_queue_status())
157
+
158
+ def set_progress_source(self, source: ProgressSource, device=None):
159
+ """
160
+ Set the source of the progress.
161
+ """
162
+ if self._progress_source == source:
163
+ self.update_source_label(source, device=device)
164
+ return
165
+ if self._progress_source is not None:
166
+ self.bec_dispatcher.disconnect_slot(
167
+ self.on_progress_update,
168
+ (
169
+ MessageEndpoints.scan_progress()
170
+ if self._progress_source == ProgressSource.SCAN_PROGRESS
171
+ else MessageEndpoints.device_progress(device=device)
172
+ ),
173
+ )
174
+ self._progress_source = source
175
+ self.bec_dispatcher.connect_slot(
176
+ self.on_progress_update,
177
+ (
178
+ MessageEndpoints.scan_progress()
179
+ if source == ProgressSource.SCAN_PROGRESS
180
+ else MessageEndpoints.device_progress(device=device)
181
+ ),
182
+ )
183
+ self.update_source_label(source, device=device)
184
+ # self.progress_started.emit()
185
+
186
+ def update_source_label(self, source: ProgressSource, device=None):
187
+ scan_text = f"Scan {self.scan_number}" if self.scan_number is not None else "Scan"
188
+ text = scan_text if source == ProgressSource.SCAN_PROGRESS else f"Device {device}"
189
+ logger.info(f"Set progress source to {text}")
190
+ self.ui.source_label.setText(text)
191
+
192
+ @SafeSlot(dict, dict)
193
+ def on_progress_update(self, msg_content: dict, metadata: dict):
194
+ """
195
+ Update the progress bar based on the progress message.
196
+ """
197
+ value = msg_content["value"]
198
+ max_value = msg_content.get("max_value", 100)
199
+ done = msg_content.get("done", False)
200
+ status: Literal["open", "paused", "aborted", "halted", "closed"] = metadata.get(
201
+ "status", "open"
202
+ )
203
+
204
+ if self.task is None:
205
+ return
206
+ self.task.update(value, max_value, done)
207
+
208
+ self.update_labels()
209
+
210
+ self.progressbar.set_maximum(self.task.max_value)
211
+ self.progressbar.state = ProgressState.from_bec_status(status)
212
+ self.progressbar.set_value(self.task.value)
213
+
214
+ if done:
215
+ self.task = None
216
+ self.progress_finished.emit()
217
+ return
218
+
219
+ @SafeProperty(bool)
220
+ def show_elapsed_time(self):
221
+ return self.ui.elapsed_time_label.isVisible()
222
+
223
+ @show_elapsed_time.setter
224
+ def show_elapsed_time(self, value):
225
+ self.ui.elapsed_time_label.setVisible(value)
226
+ if hasattr(self.ui, "dash"):
227
+ self.ui.dash.setVisible(value)
228
+
229
+ @SafeProperty(bool)
230
+ def show_remaining_time(self):
231
+ return self.ui.remaining_time_label.isVisible()
232
+
233
+ @show_remaining_time.setter
234
+ def show_remaining_time(self, value):
235
+ self.ui.remaining_time_label.setVisible(value)
236
+ if hasattr(self.ui, "dash"):
237
+ self.ui.dash.setVisible(value)
238
+
239
+ @SafeProperty(bool)
240
+ def show_source_label(self):
241
+ return self.ui.source_label.isVisible()
242
+
243
+ @show_source_label.setter
244
+ def show_source_label(self, value):
245
+ self.ui.source_label.setVisible(value)
246
+
247
+ def update_labels(self):
248
+ """
249
+ Update the labels based on the progress task.
250
+ """
251
+ if self.task is None:
252
+ return
253
+
254
+ self.ui.elapsed_time_label.setText(self.task.time_elapsed)
255
+ self.ui.remaining_time_label.setText(self.task.time_remaining)
256
+
257
+ @SafeSlot(dict, dict, verify_sender=True)
258
+ def on_queue_update(self, msg_content, metadata):
259
+ """
260
+ Update the progress bar based on the queue status.
261
+ """
262
+ if not "queue" in msg_content:
263
+ return
264
+ primary_queue_info = msg_content["queue"].get("primary", {}).get("info", [])
265
+ if len(primary_queue_info) == 0:
266
+ return
267
+ scan_info = primary_queue_info[0]
268
+ if scan_info is None:
269
+ return
270
+ if scan_info.get("status").lower() == "running" and self.task is None:
271
+ self.task = ProgressTask(parent=self)
272
+ self.progress_started.emit()
273
+
274
+ active_request_block = scan_info.get("active_request_block", {})
275
+ if active_request_block is None:
276
+ return
277
+
278
+ self.scan_number = active_request_block.get("scan_number")
279
+ report_instructions = active_request_block.get("report_instructions", [])
280
+ if not report_instructions:
281
+ return
282
+
283
+ # for now, let's just use the first instruction
284
+ instruction = report_instructions[0]
285
+
286
+ if "scan_progress" in instruction:
287
+ self.set_progress_source(ProgressSource.SCAN_PROGRESS)
288
+ elif "device_progress" in instruction:
289
+ device = instruction["device_progress"][0]
290
+ self.set_progress_source(ProgressSource.DEVICE_PROGRESS, device=device)
291
+
292
+ def cleanup(self):
293
+ if self.task is not None:
294
+ self.task.timer.stop()
295
+ self.close()
296
+ self.deleteLater()
297
+ if self._progress_source is not None:
298
+ self.bec_dispatcher.disconnect_slot(
299
+ self.on_progress_update,
300
+ (
301
+ MessageEndpoints.scan_progress()
302
+ if self._progress_source == ProgressSource.SCAN_PROGRESS
303
+ else MessageEndpoints.device_progress(device=self._progress_source.value)
304
+ ),
305
+ )
306
+ self.progressbar.close()
307
+ self.progressbar.deleteLater()
308
+ super().cleanup()
309
+
310
+
311
+ if __name__ == "__main__": # pragma: no cover
312
+ from qtpy.QtWidgets import QApplication
313
+
314
+ bec_logger.disabled_modules = ["bec_lib"]
315
+ app = QApplication([])
316
+
317
+ widget = ScanProgressBar()
318
+ widget.show()
319
+
320
+ app.exec_()