Skip to content

Commit ff797b2

Browse files
committed
Suppress selection side-effects and block signals
Avoid unwanted UI-driven side-effects when programmatically updating or selecting cameras. Add suppression flags to skip selection handlers/ actions during updates and preview startup, and wrap preview start/stop in a try/finally to restore suppression. Block widget signals while loading camera settings to prevent spurious change events, add exposure/gain to the form field groups, and skip probing when a preview is active. Also add a few diagnostic logs to help trace selection/preview behavior.
1 parent 4f77655 commit ff797b2

1 file changed

Lines changed: 90 additions & 41 deletions

File tree

dlclivegui/gui/camera_config_dialog.py

Lines changed: 90 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ def __init__(
225225
self._probe_apply_to_requested: bool = False
226226
self._probe_target_row: int | None = None
227227
self._current_edit_index: int | None = None
228+
self._suppress_selection_actions: bool = False
228229

229230
# Preview state
230231
self._preview_backend: CameraBackend | None = None
@@ -729,6 +730,8 @@ def eventFilter(self, obj, event):
729730
self.cam_fps,
730731
self.cam_width,
731732
self.cam_height,
733+
self.cam_exposure,
734+
self.cam_gain,
732735
self.cam_crop_x0,
733736
self.cam_crop_y0,
734737
self.cam_crop_x1,
@@ -824,6 +827,8 @@ def _mark_dirty(*_args):
824827
self.cam_crop_y0,
825828
self.cam_crop_x1,
826829
self.cam_crop_y1,
830+
self.cam_exposure,
831+
self.cam_gain,
827832
self.cam_width,
828833
self.cam_height,
829834
):
@@ -993,14 +998,24 @@ def _on_available_camera_double_clicked(self, item: QListWidgetItem) -> None:
993998
self._add_selected_camera()
994999

9951000
def _on_active_camera_selected(self, row: int) -> None:
1001+
if getattr(self, "_suppress_selection_change", False):
1002+
LOGGER.debug("[Selection] Suppressed currentRowChanged event at index %d.", row)
1003+
return
1004+
prev_row = self._current_edit_index
9961005
LOGGER.info(
9971006
"[Select] row=%s prev=%s preview_active=%s loading_active=%s",
9981007
row,
999-
self._current_edit_index,
1008+
prev_row,
10001009
self._preview_active,
10011010
self._loading_active,
10021011
)
1003-
prev_row = self._current_edit_index
1012+
if row is None or row < 0:
1013+
LOGGER.debug(
1014+
"[Selection] row<0 (selection cleared) ignored to avoid"
1015+
" stopping preview/loading when clicking away. row=%s",
1016+
row,
1017+
)
1018+
return
10041019

10051020
# If row is the same, ignore
10061021
if prev_row is not None and prev_row == row:
@@ -1111,36 +1126,62 @@ def _update_active_list_item(self, row: int, cam: CameraSettings) -> None:
11111126
item = self.active_cameras_list.item(row)
11121127
if not item:
11131128
return
1114-
item.setText(self._format_camera_label(cam, row))
1115-
item.setData(Qt.ItemDataRole.UserRole, cam)
1116-
item.setForeground(Qt.GlobalColor.gray if not cam.enabled else Qt.GlobalColor.black)
1117-
self._refresh_camera_labels()
1118-
self._update_button_states()
1129+
self._suppress_selection_change = True # prevent unwanted selection change events during update
1130+
try:
1131+
item.setText(self._format_camera_label(cam, row))
1132+
item.setData(Qt.ItemDataRole.UserRole, cam)
1133+
item.setForeground(Qt.GlobalColor.gray if not cam.enabled else Qt.GlobalColor.black)
1134+
self._refresh_camera_labels()
1135+
self._update_button_states()
1136+
finally:
1137+
self._suppress_selection_change = False
11191138

11201139
def _load_camera_to_form(self, cam: CameraSettings) -> None:
1121-
backend = (cam.backend or "").lower()
1122-
props = cam.properties if isinstance(cam.properties, dict) else {}
1123-
ns = props.get(backend, {}) if isinstance(props, dict) else {}
1124-
self.cam_enabled_checkbox.setChecked(cam.enabled)
1125-
self.cam_name_label.setText(cam.name)
1126-
self.cam_device_name_label.setText(str(ns.get("device_id", "")))
1127-
self.cam_index_label.setText(str(cam.index))
1128-
self.cam_backend_label.setText(cam.backend)
1129-
self._update_controls_for_backend(cam.backend)
1130-
self.cam_width.setValue(cam.width)
1131-
self.cam_height.setValue(cam.height)
1132-
self.cam_fps.setValue(cam.fps)
1133-
self.cam_exposure.setValue(cam.exposure)
1134-
self.cam_gain.setValue(cam.gain)
1135-
rot_index = self.cam_rotation.findData(cam.rotation)
1136-
if rot_index >= 0:
1137-
self.cam_rotation.setCurrentIndex(rot_index)
1138-
self.cam_crop_x0.setValue(cam.crop_x0)
1139-
self.cam_crop_y0.setValue(cam.crop_y0)
1140-
self.cam_crop_x1.setValue(cam.crop_x1)
1141-
self.cam_crop_y1.setValue(cam.crop_y1)
1142-
self.apply_settings_btn.setEnabled(True)
1143-
self._set_detected_labels(cam)
1140+
block = [
1141+
self.cam_enabled_checkbox,
1142+
self.cam_width,
1143+
self.cam_height,
1144+
self.cam_fps,
1145+
self.cam_exposure,
1146+
self.cam_gain,
1147+
self.cam_rotation,
1148+
self.cam_crop_x0,
1149+
self.cam_crop_y0,
1150+
self.cam_crop_x1,
1151+
self.cam_crop_y1,
1152+
]
1153+
for widget in block:
1154+
if hasattr(widget, "blockSignals"):
1155+
widget.blockSignals(True)
1156+
try:
1157+
backend = (cam.backend or "").lower()
1158+
props = cam.properties if isinstance(cam.properties, dict) else {}
1159+
ns = props.get(backend, {}) if isinstance(props, dict) else {}
1160+
self.cam_enabled_checkbox.setChecked(cam.enabled)
1161+
self.cam_name_label.setText(cam.name)
1162+
self.cam_device_name_label.setText(str(ns.get("device_id", "")))
1163+
self.cam_index_label.setText(str(cam.index))
1164+
self.cam_backend_label.setText(cam.backend)
1165+
self._update_controls_for_backend(cam.backend)
1166+
self.cam_width.setValue(cam.width)
1167+
self.cam_height.setValue(cam.height)
1168+
self.cam_fps.setValue(cam.fps)
1169+
self.cam_exposure.setValue(cam.exposure)
1170+
self.cam_gain.setValue(cam.gain)
1171+
rot_index = self.cam_rotation.findData(cam.rotation)
1172+
if rot_index >= 0:
1173+
self.cam_rotation.setCurrentIndex(rot_index)
1174+
self.cam_crop_x0.setValue(cam.crop_x0)
1175+
self.cam_crop_y0.setValue(cam.crop_y0)
1176+
self.cam_crop_x1.setValue(cam.crop_x1)
1177+
self.cam_crop_y1.setValue(cam.crop_y1)
1178+
self.apply_settings_btn.setEnabled(True)
1179+
self._set_detected_labels(cam)
1180+
finally:
1181+
for widget in block:
1182+
if hasattr(widget, "blockSignals"):
1183+
widget.blockSignals(False)
1184+
11441185
self.apply_settings_btn.setEnabled(False)
11451186
self._set_apply_dirty(False)
11461187

@@ -1185,7 +1226,7 @@ def _start_probe_for_camera(self, cam: CameraSettings, *, apply_to_requested: bo
11851226
requested width/height/fps with detected device values.
11861227
"""
11871228
# Don’t probe if preview is active/loading
1188-
if self._loading_active:
1229+
if self._loading_active or self._preview_active:
11891230
return
11901231

11911232
# Track probe intent
@@ -1483,6 +1524,8 @@ def _apply_camera_settings(self) -> bool:
14831524
self.cam_crop_x0,
14841525
self.cam_width,
14851526
self.cam_height,
1527+
self.cam_exposure,
1528+
self.cam_gain,
14861529
self.cam_crop_y0,
14871530
self.cam_crop_x1,
14881531
self.cam_crop_y1,
@@ -1648,18 +1691,21 @@ def _restart_preview_for_camera(self, cam: CameraSettings) -> None:
16481691
cam.backend,
16491692
cam.index,
16501693
)
1694+
self._suppress_selection_actions = True
1695+
try:
1696+
# Stop any running preview cleanly
1697+
self._stop_preview()
16511698

1652-
# Stop any running preview cleanly
1653-
self._stop_preview()
1654-
1655-
# Force preview-safe backend flags
1656-
if isinstance(cam.properties, dict):
1657-
ns = cam.properties.setdefault((cam.backend or "").lower(), {})
1658-
if isinstance(ns, dict):
1659-
ns["fast_start"] = False
1699+
# Force preview-safe backend flags
1700+
if isinstance(cam.properties, dict):
1701+
ns = cam.properties.setdefault((cam.backend or "").lower(), {})
1702+
if isinstance(ns, dict):
1703+
ns["fast_start"] = False
16601704

1661-
# Start preview without relying on selection state
1662-
self._start_preview_with_camera(cam)
1705+
# Start preview without relying on selection state
1706+
self._start_preview_with_camera(cam)
1707+
finally:
1708+
self._suppress_selection_actions = False
16631709

16641710
def _start_preview_with_camera(self, cam: CameraSettings) -> None:
16651711
"""Start preview for a given CameraSettings object."""
@@ -1759,6 +1805,9 @@ def _stop_preview(self) -> None:
17591805
self._preview_active,
17601806
getattr(getattr(self._preview_backend, "settings", None), "backend", None),
17611807
)
1808+
# Also show traceback to see who called stop_preview,
1809+
# since this should only be called from a few places.
1810+
# LOGGER.debug("[Preview] stop_preview called from: %s", "".join(traceback.format_stack(limit=6)))
17621811
# Cancel loader if running
17631812
if self._loader and self._loader.isRunning():
17641813
self._loader.request_cancel()

0 commit comments

Comments
 (0)