spacr.qt.widgets.plate_map_picker

Select one or more wells from a plate-shaped grid.

Selections are parsed and serialized through spacr.well_spec, so rows, columns, and individual wells use the same validated vocabulary in the GUI and headless APIs. Rectangular selection is exposed through select_region() for mouse-drag handling and programmatic use.

ONE GRID, NOT THREE THINGS NEAR EACH OTHER. The column numbers, the row letters and the wells all live in a single QGridLayout, every item locked to the same square cell, so a column number sits over its column and a row letter beside its row at every window size. The grid takes the width and height its square cells need and leaves the rest to a trailing empty row and column, rather than dividing whatever the window happens to give it – which is what pulls the labels away from the wells they name.

Each cell states that square in its OWN style sheet rather than through setFixedSize, which does not survive being polished under the application stylesheet. See _locked_square().

Classes

PlateMapPicker

Display a plate map and return its selection as a well specification.

Functions

well_side(→ int)

The side of a well right now, in pixels, at the user's font scale.

Module Contents

class spacr.qt.widgets.plate_map_picker.PlateMapPicker(value: str = '', layout: int = DEFAULT_LAYOUT, parent: PySide6.QtWidgets.QWidget | None = None)[source]

Bases: PySide6.QtWidgets.QDialog

Display a plate map and return its selection as a well specification.

Parameters:
  • value – the wells already chosen, as a well specification. Parsed against layout, so wells outside it are dropped rather than silently kept.

  • layout – how many wells the plate has – one of the sizes spacr.well_spec.LAYOUTS knows (6, 12, 24, 96, 384, 1536). Defaults to 384.

  • parent – parent widget.

Build the well picker over a plate layout.

Parameters:
  • value – the wells to start selected, in the text form the settings field holds.

  • layout – the plate format, in wells.

  • parent – parent widget, or None.

ask_for_layout(chosen: int | None = None) → int[source]

Choose a supported plate size and return the active layout.

begin_drag(row: int, column: int, modifiers=None) → None[source]

Anchor a drag on one well.

Parameters:
  • row – zero-based plate row of the pressed well.

  • column – zero-based plate column of the pressed well.

  • modifiers – the keyboard state at the press. With Ctrl the rectangle ADDS to what is already chosen; without it the drag REPLACES the selection, which is what every other grid in this application does.

drag_to(position) → None[source]

Preview the rectangle from the anchor to the well under position.

SHOWN WHILE DRAGGING, because a selection you cannot see until you let go is one you have to undo to correct.

A MOVE INSIDE THE PRESSED WELL IS NOT A DRAG. Every real click carries a pixel or two of pointer travel, and drawing a one-well rectangle for it both clears the rest of the selection and leaves the release to fall through as an ordinary click – which toggles the anchor straight back off, so a wobbled click lands nothing and takes the previous selection with it.

AND ONCE IT IS A DRAG IT STAYS ONE. Sweeping away from the anchor and back again ends on the anchor, and treating that as no drag would hand the release to the click and toggle off the well the rectangle had just chosen.

Parameters:

position – the pointer position in global screen coordinates (a QPoint); nothing happens unless a press has set an anchor well.

finish_drag() → bool[source]

End a drag. Returns whether one actually happened.

True tells the well to swallow its release: the rectangle is already painted, and letting the click through would toggle the anchor a second time.

select_region(start: Tuple[int, int], end: Tuple[int, int], choosing: bool | None = None) → None[source]

Select or clear the rectangle between two plate coordinates.

Parameters:
  • start (tuple of int) – Inclusive one-based row and column coordinates.

  • end (tuple of int) – Inclusive one-based row and column coordinates.

  • choosing (bool, optional) – True selects and False clears. None inverts the state of the starting well, matching spreadsheet-style drag selection.

selection() → Set[Tuple[int, int]][source]

Return selected wells as one-based (row, column) pairs.

set_layout_size(layout: int, keep: str = '') → None[source]

Rebuild the map for a plate layout and retain valid selections.

Wells outside the new layout are dropped and counted in the caption, making a layout-induced selection change visible.

Parameters:

layout – number of wells on the plate: 6, 12, 24, 96, 384 or 1536; any other value raises WellSpecError.

set_selection(cells) → None[source]

Choose exactly cells and repaint the whole plate.

Parameters:

cells – one-based (row, column) pairs, well-specification text such as "A01" or "c3", or a mixture. The field this picker edits is written in the second vocabulary, so it is worth answering to.

THE REPAINT IS NOT OPTIONAL. Signals are blocked so that setting a hundred wells says the count once rather than a hundred times, and that also silences the toggled -> _paint connection – so a well chosen without a click, which includes the picker’s own starting value and everything kept across a layout change, would be checked and still drawn empty.

value() → str[source]

Return the selection serialized for a well-specification field.

well_at(position) → Tuple[int, int] | None[source]

The well under a GLOBAL point, or None.

Global, because the pointer is over a sibling of the widget that is receiving the events – the pressed one keeps the grab.

Parameters:

position – a point in global screen coordinates (a QPoint).

spacr.qt.widgets.plate_map_picker.well_side() → int[source]

The side of a well right now, in pixels, at the user’s font scale.

THE GLYPHS GREW AND THE BOX DID NOT. WELL_SIDE was read as a raw pixel constant, so at the 200% font scale a two-digit column header wanted 30 px of width inside a 22 px cell and every one of columns 10 to 24 had its number cut in half – 15 clipped headers in every language, which is what KNOWN_OFFENDERS recorded against PlateMapPicker.

scaled_px is the house mechanism for exactly this: the font scale is one preference with two consumers, the stylesheet that sets font sizes and this, which sizes anything set in pixels from Python. A control tuned to match a text width has to go through it or the two drift apart.

READ AT CONSTRUCTION, not cached, because the preference can change while the application is running and a plate built afterwards should be pitched to the new scale.

Nested helpers

PlateMapPicker._edge_step._axis(value, low, high, bar)

The signed scroll step along one axis.

Parameters:
  • value – the pointer coordinate on this axis.

  • low – the viewport’s low edge on this axis.

  • high – the viewport’s high edge on this axis.

  • bar – the scroll bar for this axis.

Returns:

0 outside the edge band or when bar cannot scroll; otherwise a step toward the edge, capped at AUTOSCROLL_MAX_STEP.

spacr/qt/widgets/plate_map_picker.py:551