@@ -298,11 +298,9 @@ def _merge_backend_settings_back(self, opened_settings: CameraSettings) -> None:
298298 except Exception :
299299 pass
300300
301- # Merge properties (especially stable IDs) back
302301 if isinstance (opened_settings .properties , dict ):
303302 if not isinstance (target .properties , dict ):
304303 target .properties = {}
305- # shallow merge is ok; backend namespaces are nested dicts
306304 for k , v in opened_settings .properties .items ():
307305 if isinstance (v , dict ) and isinstance (target .properties .get (k ), dict ):
308306 target .properties [k ].update (v )
@@ -311,7 +309,6 @@ def _merge_backend_settings_back(self, opened_settings: CameraSettings) -> None:
311309
312310 # Update UI list item text to reflect any changes
313311 self ._update_active_list_item (row , target )
314- self ._load_camera_to_form (target )
315312
316313 # -------------------------------
317314 # UI setup
@@ -858,12 +855,18 @@ def _format_camera_label(self, cam: CameraSettings, index: int = -1) -> str:
858855
859856 def _refresh_camera_labels (self ) -> None :
860857 cam_list = getattr (self , "active_cameras_list" , None )
861- if cam_list :
858+ if not cam_list :
859+ return
860+
861+ cam_list .blockSignals (True ) # prevent unwanted selection change events during update
862+ try :
862863 for i in range (cam_list .count ()):
863864 item = cam_list .item (i )
864865 cam = item .data (Qt .ItemDataRole .UserRole )
865866 if cam :
866867 item .setText (self ._format_camera_label (cam , i ))
868+ finally :
869+ cam_list .blockSignals (False )
867870
868871 def _on_backend_changed (self , _index : int ) -> None :
869872 self ._refresh_available_cameras ()
@@ -990,8 +993,20 @@ def _on_available_camera_double_clicked(self, item: QListWidgetItem) -> None:
990993 self ._add_selected_camera ()
991994
992995 def _on_active_camera_selected (self , row : int ) -> None :
996+ LOGGER .info (
997+ "[Select] row=%s prev=%s preview_active=%s loading_active=%s" ,
998+ row ,
999+ self ._current_edit_index ,
1000+ self ._preview_active ,
1001+ self ._loading_active ,
1002+ )
9931003 prev_row = self ._current_edit_index
9941004
1005+ # If row is the same, ignore
1006+ if prev_row is not None and prev_row == row :
1007+ LOGGER .debug ("[Selection] Redundant currentRowChanged to same index %d; ignoring." , row )
1008+ return
1009+
9951010 # If switching away from a previous camera, commit pending edits first
9961011 if prev_row is not None and prev_row != row :
9971012 if not self ._commit_pending_edits (reason = "before switching camera selection" ):
@@ -1551,8 +1566,7 @@ def _cam_diff(old: CameraSettings, new: CameraSettings) -> dict:
15511566 if self ._preview_active :
15521567 if restart :
15531568 self ._append_status ("[Apply] Restarting preview to apply camera settings…" )
1554- self ._stop_preview ()
1555- QTimer .singleShot (100 , self ._start_preview ) # small delay for drivers (Basler/Pylon)
1569+ QTimer .singleShot (0 , lambda cam = new_model : self ._restart_preview_for_camera (cam ))
15561570 else :
15571571 self ._append_status ("[Apply] Applied without restart (crop/rotation update is live)." )
15581572
@@ -1627,10 +1641,72 @@ def _toggle_preview(self) -> None:
16271641 else :
16281642 self ._start_preview ()
16291643
1644+ def _restart_preview_for_camera (self , cam : CameraSettings ) -> None :
1645+ """Restart preview for a specific camera, independent of UI selection."""
1646+ LOGGER .info (
1647+ "[Preview] restarting explicitly for backend=%s idx=%s" ,
1648+ cam .backend ,
1649+ cam .index ,
1650+ )
1651+
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
1660+
1661+ # Start preview without relying on selection state
1662+ self ._start_preview_with_camera (cam )
1663+
1664+ def _start_preview_with_camera (self , cam : CameraSettings ) -> None :
1665+ """Start preview for a given CameraSettings object."""
1666+ LOGGER .info (
1667+ "[Preview] start (explicit) backend=%s idx=%s name=%s" ,
1668+ cam .backend ,
1669+ cam .index ,
1670+ cam .name ,
1671+ )
1672+
1673+ # Create loader directly from camera
1674+ self ._loader = CameraLoadWorker (cam , self )
1675+ self ._loader .progress .connect (self ._on_loader_progress )
1676+ self ._loader .success .connect (self ._on_loader_success )
1677+ self ._loader .error .connect (self ._on_loader_error )
1678+ self ._loader .canceled .connect (self ._on_loader_canceled )
1679+ self ._loader .finished .connect (self ._on_loader_finished )
1680+
1681+ self ._loading_active = True
1682+ self ._update_button_states ()
1683+
1684+ # Prepare UI
1685+ self .preview_group .setVisible (True )
1686+ self .preview_label .setText ("No preview" )
1687+ self .preview_status .clear ()
1688+ self ._show_loading_overlay ("Loading camera…" )
1689+ self ._set_preview_button_loading (True )
1690+
1691+ self ._loader .start ()
1692+
16301693 def _start_preview (self ) -> None :
16311694 """Start camera preview asynchronously (no UI freeze)."""
1632- if self ._current_edit_index is None or self ._current_edit_index < 0 :
1695+ row = self ._current_edit_index
1696+ if row is None or row < 0 :
1697+ row = self .active_cameras_list .currentRow ()
1698+
1699+ if row is None or row < 0 :
1700+ LOGGER .warning ("[Preview] No camera selected to start preview." )
16331701 return
1702+
1703+ self ._current_edit_index = row
1704+ LOGGER .info (
1705+ "[Preview] resolved start row=%s active_row=%s" ,
1706+ self ._current_edit_index ,
1707+ self .active_cameras_list .currentRow (),
1708+ )
1709+
16341710 item = self .active_cameras_list .item (self ._current_edit_index )
16351711 if not item :
16361712 return
@@ -1651,6 +1727,11 @@ def _start_preview(self) -> None:
16511727 self ._stop_preview ()
16521728 # if self._loader and self._loader.isRunning():
16531729 # self._loader.request_cancel()
1730+ # Never use probe or fast_start mode
1731+ if isinstance (cam .properties , dict ):
1732+ ns = cam .properties .get ((cam .backend or "" ).lower (), {})
1733+ if isinstance (ns , dict ):
1734+ ns ["fast_start" ] = False
16541735 # Create worker
16551736 self ._loader = CameraLoadWorker (cam , self )
16561737 self ._loader .progress .connect (self ._on_loader_progress )
0 commit comments