spacr.qt.widgets.object_grid_binding

Keeping the per-object grid and the flat settings form saying the same thing.

The grid in spacr.qt.widgets.object_settings_grid edits VALUES; the settings panel is a dictionary of WIDGETS, and the pipeline reads the widgets. Mounting the grid without a binding would give a screen two answers to the same question – the table showing what the user typed and collect() still returning what the widget holds – and the run would silently use the second one. That is a settings file that means something other than what it looks like, which is worse than the flat form the grid replaces.

So the grid never becomes the source of truth. It is a VIEW that writes through: every edit lands in the widget behind it, and the widget stays what the pipeline reads. Nothing downstream of collect() learns that the grid exists, so no settings file, notebook or spacr-run invocation changes.

WHY THE PANEL IS DUCK-TYPED. This asks for two methods – collect and set_value_for_key – and not for a class. That keeps the binding testable against a dictionary rather than against a built screen, and it is the whole of what the binding needs.

Classes

ObjectGridBinding

Bind a per-object grid to the settings panel behind it.

Module Contents

class spacr.qt.widgets.object_grid_binding.ObjectGridBinding(grid, panel, parent=None)[source]

Bases: PySide6.QtCore.QObject

Bind a per-object grid to the settings panel behind it.

Parameters:
  • grid – the ObjectSettingsGrid to drive.

  • panel – anything offering collect() and set_value_for_key(key, value).

  • parent – parent object.

Bind a per-object grid to the settings panel beside it.

The re-entrancy guard is what stops the table visibly rebuilding under a cursor that is still in a cell: writing a value into a widget makes that widget emit, and a screen that reseeds the grid on every widget change would rebuild it mid-edit. No value depends on the guard – the write reads the grid once, before the first widget moves – so a reseed halfway cannot drop an edit either way.

Parameters:
  • grid – the object settings grid.

  • panel – the settings panel it mirrors.

  • parent – parent object, or None.

follow_the_form() → int[source]

Make the form’s own fields write INTO the table.

THE OTHER HALF OF A TWO-WAY BINDING, and the half that was missing: the grid wrote through to the widgets, but nothing told the grid when a widget moved, so a value changed anywhere else – the flat row for an object the table does not claim, a preset, the Live Preview, a settings file poured in – left the table showing the old answer.

WHY NOT JUST RESEED. seed rebuilds the whole table, which is 30 ms on Mask and 100 ms once there are ten organelles. On a textChanged that is per KEYSTROKE, and it would also take the cursor out of whatever cell was being typed into. One cell is written instead.

Returns:

how many widgets were newly connected.

owned_keys() → FrozenSet[str][source]

The settings keys the grid answers, as the panel now stands.

Read from the panel every time rather than remembered, because a panel hides the settings of an object whose channel names no plane and the grid must not claim a key that is no longer there.

Columns hidden because their channel is unset are still claimed, so their rows stay off the flat form; their channels are not claimed, because a hidden object’s channel on the form is how it is brought back.

seed() → None[source]

Show the panel’s current answers in the grid.

Idempotent, and safe to call whenever something else has written the panel – a settings file being loaded, a preset applied, the Live Preview propagating what it tuned.

write_through() → Dict[str, Any][source]

Write every changed cell into its widget; return what changed.

ONLY WHAT DIFFERS. Setting a widget to the value it already holds still makes it emit, and a panel that re-validates on every emit would do the whole form’s work on each keystroke in the table.