@@ -171,6 +171,15 @@ class PositionTable(DataTableWidget):
171171 def __init__ (self , rows : int = 0 , parent : QWidget | None = None ):
172172 super ().__init__ (rows , parent )
173173
174+ # track whether a global absolute grid disables all x/y
175+ self ._global_xy_disabled = False
176+
177+ # when a sub-sequence changes, update x/y enabled state for that row
178+ if model := self .table ().model ():
179+ model .rowsInserted .connect (self ._on_table_rows_inserted )
180+ if (rows := self .table ().rowCount ()) > 0 :
181+ self ._on_table_rows_inserted (None , 0 , rows - 1 )
182+
174183 self .include_z = QCheckBox ("Include Z" )
175184 self .include_z .setChecked (True )
176185 self .include_z .toggled .connect (self ._on_include_z_toggled )
@@ -346,6 +355,64 @@ def load(self, file: str | Path | None = None) -> None:
346355 except Exception as e : # pragma: no cover
347356 raise ValueError (f"Failed to load MDASequence file: { src } " ) from e
348357
358+ def setXYEnabled (self , enabled : bool ) -> None :
359+ """Disable or enable X/Y columns for all rows (e.g. global absolute grid)."""
360+ self ._global_xy_disabled = not enabled
361+ table = self .table ()
362+ seq_col = table .indexOf (self .SEQ )
363+ tip = "X/Y defined by the global absolute grid plan." if not enabled else ""
364+ for row in range (table .rowCount ()):
365+ # skip rows that have their own absolute sub-sequence grid
366+ if enabled :
367+ wdg = table .cellWidget (row , seq_col )
368+ if isinstance (wdg , MDAButton ) and _seq_has_absolute_grid (wdg .value ()):
369+ continue
370+ self ._set_row_xy_enabled (row , enabled , tip )
371+
372+ # ------------------- sub-sequence grid helpers -------------------
373+
374+ def _on_table_rows_inserted (self , parent : object , start : int , end : int ) -> None :
375+ """Connect MDAButton.valueChanged for newly inserted rows."""
376+ table = self .table ()
377+ seq_col = table .indexOf (self .SEQ )
378+ tip = "X/Y defined by the global absolute grid plan."
379+ for row in range (start , end + 1 ):
380+ wdg = table .cellWidget (row , seq_col )
381+ if isinstance (wdg , MDAButton ):
382+ wdg .valueChanged .connect (self ._on_sub_seq_changed )
383+ # apply global disable to newly added rows
384+ if self ._global_xy_disabled :
385+ self ._set_row_xy_enabled (row , False , tip )
386+
387+ def _on_sub_seq_changed (self ) -> None :
388+ """Disable x/y for the row if its sub-sequence has an absolute grid."""
389+ btn = self .sender ()
390+ if not isinstance (btn , MDAButton ):
391+ return
392+ table = self .table ()
393+ seq_col = table .indexOf (self .SEQ )
394+ for row in range (table .rowCount ()):
395+ if table .cellWidget (row , seq_col ) is btn :
396+ has_abs = _seq_has_absolute_grid (btn .value ())
397+ # global disable takes precedence
398+ if self ._global_xy_disabled and not has_abs :
399+ return
400+ tip = (
401+ "X/Y defined by the absolute grid in the sub-sequence."
402+ if has_abs
403+ else ""
404+ )
405+ self ._set_row_xy_enabled (row , not has_abs , tip )
406+ break
407+
408+ def _set_row_xy_enabled (self , row : int , enabled : bool , tip : str = "" ) -> None :
409+ """Enable/disable x/y widgets for a single row."""
410+ table = self .table ()
411+ for col_info in (self .X , self .Y ):
412+ if wdg := table .cellWidget (row , table .indexOf (col_info )):
413+ wdg .setEnabled (enabled )
414+ wdg .setToolTip (tip )
415+
349416 # ------------------------- Private API -------------------------
350417
351418 def _on_include_z_toggled (self , checked : bool ) -> None :
@@ -357,3 +424,8 @@ def _on_af_per_position_toggled(self, checked: bool) -> None:
357424 af_col = self .table ().indexOf (self .AF )
358425 self .table ().setColumnHidden (af_col , not checked )
359426 self .valueChanged .emit ()
427+
428+
429+ def _seq_has_absolute_grid (seq : useq .MDASequence | None ) -> bool :
430+ """Return True if the sequence has an absolute grid plan."""
431+ return bool (seq and seq .grid_plan and not seq .grid_plan .is_relative )
0 commit comments