@@ -71,8 +71,15 @@ def __init__(
7171 self ._settings_scroll_contents : QWidget | None = None
7272
7373 self ._setup_ui ()
74- self ._populate_from_settings ()
7574 self ._connect_signals ()
75+ self ._populate_from_settings ()
76+ self ._post_init_sync_selection ()
77+
78+ def _post_init_sync_selection (self ) -> None :
79+ """Ensure current selection state is reflected in _current_edit_index."""
80+ row = self .active_cameras_list .currentRow ()
81+ if row >= 0 :
82+ self ._on_active_camera_selected (row )
7683
7784 @property
7885 def dlc_camera_id (self ) -> str | None :
@@ -94,6 +101,9 @@ def showEvent(self, event):
94101 self ._working_settings = self ._multi_camera_settings .model_copy (deep = True )
95102 self ._current_edit_index = None
96103
104+ self ._populate_from_settings ()
105+ self ._post_init_sync_selection ()
106+
97107 # Maintain overlay geometry when resizing
98108 def resizeEvent (self , event ):
99109 super ().resizeEvent (event )
@@ -126,7 +136,7 @@ def eventFilter(self, obj, event):
126136 # Intercept Enter in FPS and crop spinboxes
127137 if event .type () == QEvent .KeyPress and isinstance (event , QKeyEvent ):
128138 if event .key () in (Qt .Key_Return , Qt .Key_Enter ):
129- if obj in (
139+ spinboxes = (
130140 self .cam_fps ,
131141 self .cam_width ,
132142 self .cam_height ,
@@ -136,15 +146,22 @@ def eventFilter(self, obj, event):
136146 self .cam_crop_y0 ,
137147 self .cam_crop_x1 ,
138148 self .cam_crop_y1 ,
139- ):
140- # Commit any pending text → value
149+ )
150+
151+ def _matches (sb ):
152+ return obj is sb or obj is getattr (sb , "lineEdit" , lambda : None )()
153+
154+ if any (_matches (sb ) for sb in spinboxes ):
141155 try :
142- obj .interpretText ()
156+ # If event came from lineEdit, interpretText still belongs to the spinbox
157+ for sb in spinboxes :
158+ if _matches (sb ):
159+ sb .interpretText ()
160+ break
143161 except Exception :
144162 pass
145- # Apply settings to persist crop/FPS to CameraSettings
163+
146164 self ._apply_camera_settings ()
147- # Consume so OK isn't triggered
148165 return True
149166
150167 return super ().eventFilter (obj , event )
@@ -226,6 +243,24 @@ def _on_close_cleanup(self) -> None:
226243 # -------------------------------
227244 def _setup_ui (self ) -> None :
228245 setup_camera_config_dialog_ui (self )
246+ for sb in (
247+ self .cam_fps ,
248+ self .cam_width ,
249+ self .cam_height ,
250+ self .cam_exposure ,
251+ self .cam_gain ,
252+ self .cam_crop_x0 ,
253+ self .cam_crop_y0 ,
254+ self .cam_crop_x1 ,
255+ self .cam_crop_y1 ,
256+ ):
257+ try :
258+ sb .installEventFilter (self )
259+ le = sb .lineEdit ()
260+ if le is not None :
261+ le .installEventFilter (self )
262+ except Exception :
263+ pass
229264
230265 def _position_scan_overlay (self ) -> None :
231266 """Position scan overlay to cover the available_cameras_list area."""
@@ -325,8 +360,8 @@ def _update_button_states(self) -> None:
325360
326361 self .preview_btn .setEnabled (has_active_selection or self ._preview .state == PreviewState .LOADING )
327362
328- available_row = self .available_cameras_list .currentRow ()
329- self .add_camera_btn .setEnabled (available_row >= 0 and not scan_running )
363+ self .available_cameras_list .currentRow ()
364+ self .add_camera_btn .setEnabled (( self . _selected_detected_camera () is not None ) and not scan_running )
330365
331366 def _sync_preview_ui (self ) -> None :
332367 """Update buttons/overlays based on preview state only."""
@@ -395,6 +430,16 @@ def _format_camera_label(self, cam: CameraSettings, index: int = -1) -> str:
395430 dlc_indicator = " [DLC]" if this_id == self ._dlc_camera_id and cam .enabled else ""
396431 return f"{ status } { cam .name } [{ cam .backend } :{ cam .index } ]{ dlc_indicator } "
397432
433+ def _selected_detected_camera (self ) -> DetectedCamera | None :
434+ row = self .available_cameras_list .currentRow ()
435+ if row < 0 :
436+ return None
437+ item = self .available_cameras_list .item (row )
438+ if not item :
439+ return None
440+ detected = item .data (Qt .ItemDataRole .UserRole )
441+ return detected if isinstance (detected , DetectedCamera ) else None
442+
398443 def _update_active_list_item (self , row : int , cam : CameraSettings ) -> None :
399444 """Refresh the active camera list row text and color."""
400445 item = self .active_cameras_list .item (row )
@@ -479,7 +524,6 @@ def _is_scan_running(self) -> bool:
479524 def _set_scan_state (self , state : CameraScanState , message : str | None = None ) -> None :
480525 """Single source of truth for scan-related UI controls."""
481526 self ._scan_state = state
482-
483527 scanning = state in (CameraScanState .RUNNING , CameraScanState .CANCELING )
484528
485529 # Overlay message
@@ -500,10 +544,8 @@ def _set_scan_state(self, state: CameraScanState, message: str | None = None) ->
500544 # Disable discovery inputs while scanning
501545 self .backend_combo .setEnabled (not scanning )
502546 self .refresh_btn .setEnabled (not scanning )
503-
504547 # Available list + add flow blocked while scanning (structure edits disallowed)
505548 self .available_cameras_list .setEnabled (not scanning )
506- self .add_camera_btn .setEnabled (False if scanning else (self .available_cameras_list .currentRow () >= 0 ))
507549
508550 self ._update_button_states ()
509551
@@ -644,8 +686,7 @@ def _on_available_camera_selected(self, row: int) -> None:
644686 if self ._scan_worker and self ._scan_worker .isRunning ():
645687 self .add_camera_btn .setEnabled (False )
646688 return
647- item = self .available_cameras_list .item (row ) if row >= 0 else None
648- detected = item .data (Qt .ItemDataRole .UserRole ) if item else None
689+ detected = self ._selected_detected_camera ()
649690 self .add_camera_btn .setEnabled (isinstance (detected , DetectedCamera ))
650691
651692 def _on_available_camera_double_clicked (self , item : QListWidgetItem ) -> None :
0 commit comments