@@ -166,10 +166,6 @@ def __init__(self, cam: CameraSettings, parent: QWidget | None = None):
166166 ns = self ._cam .properties .setdefault (self ._cam .backend .lower (), {})
167167 if isinstance (ns , dict ):
168168 ns ["fast_start" ] = False
169- # Basler implements transforms in the backend
170- # but preview already takes care of rotation/crop
171- # so we disable transform application in probe to avoid double transforms and speed up probe
172- ns ["apply_transforms" ] = False
173169
174170 def request_cancel (self ):
175171 self ._cancel = True
@@ -975,10 +971,25 @@ def _on_available_camera_double_clicked(self, item: QListWidgetItem) -> None:
975971 self ._add_selected_camera ()
976972
977973 def _on_active_camera_selected (self , row : int ) -> None :
974+ prev_row = self ._current_edit_index
975+
976+ # If switching away from a previous camera, commit pending edits first
977+ if prev_row is not None and prev_row != row :
978+ if not self ._commit_pending_edits (reason = "before switching camera selection" ):
979+ # Revert selection back to previous row so the user stays on the invalid camera
980+ try :
981+ self .active_cameras_list .blockSignals (True )
982+ self .active_cameras_list .setCurrentRow (prev_row )
983+ finally :
984+ self .active_cameras_list .blockSignals (False )
985+ return
986+
978987 # Stop any running preview when selection changes
979988 if self ._preview_active :
980989 self ._stop_preview ()
990+
981991 self ._current_edit_index = row
992+
982993 self ._update_button_states ()
983994 if row < 0 or row >= self .active_cameras_list .count ():
984995 self ._clear_settings_form ()
@@ -1301,6 +1312,8 @@ def _on_probe_finished(self) -> None:
13011312 self ._probe_worker = None
13021313
13031314 def _add_selected_camera (self ) -> None :
1315+ if not self ._commit_pending_edits (reason = "before adding a new camera" ):
1316+ return
13041317 row = self .available_cameras_list .currentRow ()
13051318 if row < 0 :
13061319 return
@@ -1356,6 +1369,8 @@ def _add_selected_camera(self) -> None:
13561369 self ._start_probe_for_camera (new_cam )
13571370
13581371 def _remove_selected_camera (self ) -> None :
1372+ if not self ._commit_pending_edits (reason = "before removing a camera" ):
1373+ return
13591374 row = self .active_cameras_list .currentRow ()
13601375 if row < 0 :
13611376 return
@@ -1368,6 +1383,8 @@ def _remove_selected_camera(self) -> None:
13681383 self ._update_button_states ()
13691384
13701385 def _move_camera_up (self ) -> None :
1386+ if not self ._commit_pending_edits (reason = "before reordering cameras" ):
1387+ return
13711388 row = self .active_cameras_list .currentRow ()
13721389 if row <= 0 :
13731390 return
@@ -1379,6 +1396,8 @@ def _move_camera_up(self) -> None:
13791396 self ._refresh_camera_labels ()
13801397
13811398 def _move_camera_down (self ) -> None :
1399+ if not self ._commit_pending_edits (reason = "before reordering cameras" ):
1400+ return
13821401 row = self .active_cameras_list .currentRow ()
13831402 if row < 0 or row >= self .active_cameras_list .count () - 1 :
13841403 return
@@ -1389,10 +1408,39 @@ def _move_camera_down(self) -> None:
13891408 cams [row ], cams [row + 1 ] = cams [row + 1 ], cams [row ]
13901409 self ._refresh_camera_labels ()
13911410
1392- def _apply_camera_settings (self ) -> None :
1411+ def _commit_pending_edits (self , * , reason : str = "" ) -> bool :
1412+ """
1413+ Auto-apply pending edits (if any) before context-changing actions.
1414+ Returns True if it's safe to proceed, False if validation failed.
1415+ """
1416+ # No selection → nothing to commit
1417+ if self ._current_edit_index is None or self ._current_edit_index < 0 :
1418+ return True
1419+
1420+ # If Apply button isn't enabled, assume no pending edits
1421+ if not self .apply_settings_btn .isEnabled ():
1422+ return True
1423+
1424+ try :
1425+ self ._append_status (f"[Auto-Apply] Committing pending edits ({ reason } )…" )
1426+ ok = self ._apply_camera_settings ()
1427+ return bool (ok )
1428+ except Exception as exc :
1429+ # _apply_camera_settings already shows a QMessageBox in many cases,
1430+ # but we add a clear guardrail here in case it doesn't.
1431+ QMessageBox .warning (
1432+ self ,
1433+ "Unsaved / Invalid Settings" ,
1434+ "Your current camera settings are not valid and cannot be applied yet.\n \n "
1435+ "Please fix the highlighted fields (e.g. crop rectangle) or press Reset.\n \n "
1436+ f"Details: { exc } " ,
1437+ )
1438+ return False
1439+
1440+ def _apply_camera_settings (self ) -> bool :
13931441 if self ._loading_active :
13941442 self ._append_status ("[Apply] Preview is loading; please wait or cancel loading first." )
1395- return
1443+ return False
13961444 try :
13971445 for sb in (
13981446 self .cam_fps ,
@@ -1487,9 +1535,12 @@ def _cam_diff(old: CameraSettings, new: CameraSettings) -> dict:
14871535 else :
14881536 self ._append_status ("[Apply] Applied without restart (crop/rotation update is live)." )
14891537
1538+ return True
1539+
14901540 except Exception as exc :
14911541 LOGGER .exception ("Apply camera settings failed" )
14921542 QMessageBox .warning (self , "Apply Settings Error" , str (exc ))
1543+ return False
14931544
14941545 def _update_button_states (self ) -> None :
14951546 active_row = self .active_cameras_list .currentRow ()
@@ -1503,6 +1554,15 @@ def _update_button_states(self) -> None:
15031554 self .add_camera_btn .setEnabled (available_row >= 0 )
15041555
15051556 def _on_ok_clicked (self ) -> None :
1557+ # Auto-apply pending edits before saving
1558+ if not self ._commit_pending_edits (reason = "before going back to the main window" ):
1559+ return
1560+ try :
1561+ if self .apply_settings_btn .isEnabled ():
1562+ self ._append_status ("[OK button] Auto-applying pending settings before closing dialog." )
1563+ self ._apply_camera_settings ()
1564+ except Exception :
1565+ LOGGER .exception ("[OK button] Auto-apply failed" )
15061566 self ._stop_preview ()
15071567 active = self ._working_settings .get_active_cameras ()
15081568 if self ._working_settings .cameras and not active :
0 commit comments