diff --git a/bec_widgets/widgets/control/scan_control/scan_control.py b/bec_widgets/widgets/control/scan_control/scan_control.py index 4adcbdec..e189f8f1 100644 --- a/bec_widgets/widgets/control/scan_control/scan_control.py +++ b/bec_widgets/widgets/control/scan_control/scan_control.py @@ -469,6 +469,7 @@ class ScanControl(BECWidget, QWidget): """Clears the scan control layout from GuiGroups and ArgGroups boxes.""" if self.arg_box is not None: self.layout.removeWidget(self.arg_box) + self.arg_box.close() self.arg_box.deleteLater() self.arg_box = None if self.kwarg_boxes != []: @@ -477,6 +478,7 @@ class ScanControl(BECWidget, QWidget): def remove_kwarg_boxes(self): for box in self.kwarg_boxes: self.layout.removeWidget(box) + box.close() box.deleteLater() self.kwarg_boxes = [] @@ -554,15 +556,6 @@ class ScanControl(BECWidget, QWidget): def cleanup(self): """Cleanup the scan control widget.""" - self.button_stop_scan.cleanup() - if self.arg_box: - for widget in self.arg_box.widgets: - if hasattr(widget, "cleanup"): - widget.cleanup() - for kwarg_box in self.kwarg_boxes: - for widget in kwarg_box.widgets: - if hasattr(widget, "cleanup"): - widget.cleanup() super().cleanup() diff --git a/bec_widgets/widgets/control/scan_control/scan_group_box.py b/bec_widgets/widgets/control/scan_control/scan_group_box.py index 85ee0031..c2f497be 100644 --- a/bec_widgets/widgets/control/scan_control/scan_group_box.py +++ b/bec_widgets/widgets/control/scan_control/scan_group_box.py @@ -1,5 +1,6 @@ from typing import Literal, Sequence +import shiboken6 from bec_lib.logger import bec_logger from bec_qthemes import material_icon from qtpy.QtCore import Property, Qt, Signal, Slot @@ -239,6 +240,13 @@ class ScanGroupBox(QGroupBox): # margins. Host layouts should top-align or stretch below the box; ScanControl uses # layout.setAlignment(Qt.AlignTop). + def closeEvent(self, event): + for widget in self.widgets: + if shiboken6.isValid(widget): + widget.close() + widget.deleteLater() + super().closeEvent(event) + def init_box(self, config: dict): box_name = config.get("name", "ScanGroupBox") self.inputs = config.get("inputs", {}) diff --git a/tests/unit_tests/test_scan_control.py b/tests/unit_tests/test_scan_control.py index a5991922..2330106e 100644 --- a/tests/unit_tests/test_scan_control.py +++ b/tests/unit_tests/test_scan_control.py @@ -648,6 +648,23 @@ def test_current_scan(scan_control, mocked_client): assert scan_control.current_scan == new_scan +def test_scan_switch_runs_cleanup_on_previous_inputs(scan_control): + """Switching scans tears down the old group boxes; the BECWidget inputs inside + (device comboboxes) must go through close() so their cleanup runs, instead of + being destroyed by deleteLater() without cleanup.""" + scan_control.comboBox_scan_selection.setCurrentText("line_scan") + old_inputs = [w for w in scan_control.arg_box.widgets if hasattr(w, "_destroyed")] + assert old_inputs, "line_scan arg box should contain at least one BECWidget input" + assert all(not w._destroyed for w in old_inputs) + + scan_control.comboBox_scan_selection.setCurrentText("grid_scan") + + # closeEvent ran cleanup and flagged each old input as destroyed + assert all(w._destroyed for w in old_inputs) + register = scan_control.rpc_register + assert all(not register.object_is_registered(w) for w in old_inputs) + + @pytest.mark.parametrize("scan_name", ["line_scan", "grid_scan"]) def test_on_scan_selected(scan_control, scan_name): expected_scan_info = available_scans_message.resource[scan_name]