spacr.qt.widgets.availability_panel¶
Explain unavailable options and offer an applicable next step.
For an unavailable entry this module states why, links the documentation and – where this environment allows it – offers to make the entry available. The code is shared because the regression backend picker and the Image UMAP’s GPU acceleration need the same panel, and a second interactive tooltip would need its own hover, focus and dismissal handling.
Why this is not a QToolTip: PySide6 reports:
QToolTip has a linkActivated signal: False
QToolTip is a QWidget: False
QToolTip only paints. It renders rich text and nothing inside it can be
clicked, hovered or focused, so a link drawn in it cannot be pressed. What
works is a frameless tooltip-styled QLabel with
Qt.TextBrowserInteraction: its linkActivated carries the href, so
api opens the documentation and install opens the offer.
The row itself stays disabled. QStandardItem.setEnabled(False) leaves
ItemIsSelectable set – Qt will not activate a disabled row, but a
model-level selection can still land on it – so a caller must clear that flag
too. disable_combo_row() does both and is what the callers use.
An interactive tooltip has three requirements, handled here as follows:
It must not disappear while the pointer moves toward it. The pointer has to cross the gap between the row and the panel, and a plain leave-event dismissal closes it mid-journey, which makes the Install link unreachable. The hide is a timer, the timer is cancelled on entry, and while it is pending the cursor is checked against the corridor – the union of the anchor’s rectangle and the panel’s – so travelling through the gap re-arms the timer instead of firing it. See
AvailabilityPanel. _maybe_hide().It must be dismissable. Escape, a click anywhere outside it, and moving well clear of the corridor all close it.
It must be reachable by keyboard, and that cannot be inherited from the row because the row is disabled.
AvailabilityPanel.open_for()is the explicit keyboard route: it shows the panel activated with the first link focused, Up/Down move between the unavailable entries, Escape closes it and returns focus to wherever it came from. A keyboard-opened panel is pinned and never closes on a hover timer.
Pressing Install runs run_install_offer(), which has three outcomes –
see spacr.updater.InstallOffer. The dry-run report is shown before
anything is installed, and a plan that would move numpy, torch, pandas or
scikit-learn is refused by default and needs a second confirmation naming what
moves.
Classes¶
The hover panel a greyed-out option opens: reason, API, Install. |
Functions¶
|
Disable a combo row and remove it from keyboard selection. |
|
Open the shared panel on |
|
The link word for an offer's |
|
Handle the three outcomes of an install offer in one place. |
Module Contents¶
- class spacr.qt.widgets.availability_panel.AvailabilityPanel[source]¶
Bases:
PySide6.QtWidgets.QFrameThe hover panel a greyed-out option opens: reason, API, Install.
Access through
instance()– it is a process-wide singleton, so one panel serves every control that has an unavailable option and there is only ever one of them on screen.- Signal api_requested:
the API link was pressed; carries the URL.
- Signal install_requested:
the Install link was pressed; carries the
spacr.updater.InstallOfferof the entry on screen.- Signal dismissed:
the panel closed by Escape, by a click away, or by the pointer leaving the corridor.
Build the availability popup.
A
Qt.Toolwindow rather than aQt.ToolTipone: a tooltip window can never take focus, and the keyboard route into this panel needs it to. The two link words are separate labels so that “Install sits to the right of the API link” is a fact about geometry a test can measure, rather than a claim about a string.- api_link() PySide6.QtWidgets.QLabel[source]¶
The API word – exposed so a test can measure where it sits.
- corridor() PySide6.QtCore.QRect | None[source]¶
The rectangle the pointer is allowed to be inside while travelling.
The union of the anchor’s rectangle and the panel’s, the region a pointer moving from one to the other passes through. Hiding on the anchor’s leave event would close the panel before the pointer reaches it.
- current_offer()[source]¶
The
spacr.updater.InstallOfferon screen, orNone.
- enterEvent(event)[source]¶
The pointer arrived: the panel stays.
- Parameters:
event – the enter event, passed on to the base class after any pending hide is cancelled.
- eventFilter(obj, event)[source]¶
A press anywhere outside the panel dismisses it.
- Parameters:
obj – the watched object; the filter is installed on the application, so this is whatever received the event. Not read.
event – the event; only a mouse press while the panel is visible is acted on, using its global position. Always returns
Falseso the press still reaches its target.
- hideEvent(event)[source]¶
Drop the app filter whenever the panel leaves the screen.
- Parameters:
event – the hide event, passed on to the base class.
- classmethod instance() AvailabilityPanel[source]¶
The process-wide panel, created on first use.
- is_pinned() bool[source]¶
Whether the panel was opened by keyboard. A pinned panel ignores hover timers.
- keyPressEvent(event)[source]¶
Escape closes; Up/Down move between the entries.
- Parameters:
event – the key event; Escape, Up/Left and Down/Right are accepted here (the arrows only with more than one entry), and any other key goes to the base class.
- leaveEvent(event)[source]¶
The pointer left: start the interruptible hide.
- Parameters:
event – the leave event, passed on to the base class after a 100 ms hide is scheduled.
- open_for(anchor: PySide6.QtWidgets.QWidget, entries, index: int = 0, *, anchor_rect: PySide6.QtCore.QRect | None = None) None[source]¶
The keyboard route: show the panel pinned, activated and focused.
The row that would normally carry this help is disabled, so it cannot be tabbed to and nothing can be inherited from it. A caller wires this to a key on the control that is focusable – the combo itself – and the panel takes it from there: Tab moves between API and Install, Enter presses one, Up/Down move to the next unavailable entry, Escape closes and hands focus back.
- Parameters:
anchor – the widget the panel is docked under; see
show_for().entries – availability mappings (
{title, reason, url, offer}) asshow_for()takes them; an empty list shows nothing.
- set_install_handler(slot) None[source]¶
Make
slotthe only receiver ofinstall_requested.The panel is a process-wide singleton with two callers. A plain
connecton every show accumulates receivers, so one press would eventually run the regression picker’s install and the Image UMAP’s; a plaindisconnect()warns when nothing is connected yet. Holding the current slot and replacing it does neither. A previous slot bound to a widget that has since been deleted is simply dropped.- Parameters:
slot – callable taking the current entry’s
offer, orNoneto leave the signal with no receiver.
- show_entry(index: int) None[source]¶
Move to another of the entries without moving the panel.
- Parameters:
index – entry position; wraps around modulo the number of entries, so
-1is the last one.
- show_for(anchor: PySide6.QtWidgets.QWidget, entries, index: int = 0, *, anchor_rect: PySide6.QtCore.QRect | None = None, pinned: bool = False, immediate: bool = False) None[source]¶
Show the panel for
entries[index], docked underanchor.- Parameters:
anchor – the widget the panel belongs to. Used for placement and as one half of the corridor the pointer may cross.
entries – mappings as
spacr.regression_backends.availability_entry()returns –{title, reason, url, offer}. More than one makes the panel cyclable with Up/Down when it is pinned.index – which of them to show.
anchor_rect – the anchor’s rectangle in GLOBAL coordinates, when the thing being explained is smaller than the widget – a single row of an open combo popup, for instance.
pinned – opened by keyboard. See
open_for().immediate – explicit click/keyboard request, bypassing hover delay.
- spacr.qt.widgets.availability_panel.disable_combo_row(combo, index: int, *, tooltip: str = '') None[source]¶
Disable a combo row and remove it from keyboard selection.
QStandardItem.setEnabled(False)leavesQt.ItemIsSelectableset in the item’s flags. Qt refuses to activate a disabled row from the popup, so the mouse route is closed, but a model-level selection (setCurrentIndex, a settings CSV round-trip, a view’s own selection model) can still land on it. An unavailable entry must not become the selected value, so this function clears both flags.A disabled item retains its
Qt.ToolTipRole, so the explanatory tooltip remains available on hover.- Parameters:
combo –
QComboBoxbacked by aQStandardItemModel.index – Row to disable.
tooltip – Explanatory tooltip; an empty string leaves it unchanged.
- spacr.qt.widgets.availability_panel.explain(anchor, entries, index: int = 0, *, pinned: bool = False, parent=None, anchor_rect=None, on_installed=None)[source]¶
Open the shared panel on
entries[index]with Install already wired.The two-line form for a caller that has one control and one entry – the Image UMAP’s GPU acceleration is one:
from spacr.gpu_reduce import availability_entry explain(self._gpu_button, [availability_entry()], parent=self)
The panel is a process-wide singleton, so the Install connection is replaced on every call rather than accumulated; two callers connected at once would both answer one press.
- Parameters:
anchor – the widget the panel docks under.
entries – mappings from
availability_entry– seeAvailabilityPanel.show_for().pinned – open it by the keyboard route (activated and focused).
parent – what the install dialogs are parented to; defaults to
anchor.on_installed – called with the offer after a successful install, for a caller that has to re-probe.
- Returns:
the panel, or
Nonewhenentriesis empty.
- spacr.qt.widgets.availability_panel.install_word_for(action) str[source]¶
The link word for an offer’s
action;''when there is none.- Parameters:
action – the offer’s action,
"install","elsewhere","impossible"or"ready"(which gives''); any other value gives"What it needs".
- spacr.qt.widgets.availability_panel.run_install_offer(parent, offer, *, confirm=None, inform=None, dry_run=None, install=None) str[source]¶
Handle the three outcomes of an install offer in one place.
Installable here – the dry-run report is shown first, in full, and the install runs only on confirmation. A plan that would move numpy, torch, pandas or scikit-learn is refused by default and needs a second confirmation naming what moves.
Not here, but possible elsewhere – the environment that would take it is described and nothing is run.
Not possible – stated, with the recipe, and nothing is run.
Every side effect is an injected callable so the whole flow can be driven without a screen:
tests/qt/conftest.pymakes a static modal raise on purpose.- Parameters:
parent – the widget dialogs are parented to.
offer – a
spacr.updater.InstallOffer.confirm –
(title, text) -> bool. Defaults to aQMessageBox.questionwhose default button is No.inform –
(title, text) -> None. Defaults toQMessageBox.information.dry_run –
(requirement) -> DryRun. Defaults tospacr.updater.dry_run_install()behind a progress dialog – the resolver talks to the network and has been measured taking minutes.install –
(command) -> (code, output). Defaults tospacr.updater.run_install_command().
- Returns:
one of
'ready','explained','declined','refused','installed','failed'.
Nested helpers¶
- _default_confirm._confirm(title: str, text: str) bool¶
Ask a yes/no question in a modal box.
spacr/qt/widgets/availability_panel.py:774
- _default_dry_run._run(requirement)¶
Dry-run an install and hand the outcome back.
spacr/qt/widgets/availability_panel.py:812
- _default_dry_run._run._work()¶
Do the dry run. Called off the GUI thread.
spacr/qt/widgets/availability_panel.py:816
- _default_inform._inform(title: str, text: str) None¶
Say something in a modal box.
spacr/qt/widgets/availability_panel.py:789
- explain._install(offer)¶
Run one install offer and report what happened.
spacr/qt/widgets/availability_panel.py:873