spacr.qt.gui_scale

The whole-GUI scale, applied live: one factor for every size spaCR sets (471).

WHAT IT IS. “GUI scale” in Preferences (10 % to 200 %, default 100 %) scales widget sizes, fixed sizes, margins, spacing, style-sheet sizes (fonts and paddings included), icons, splitter sizes and figure dpi – and it does so while spaCR runs, with no restart.

HOW, WITHOUT TOUCHING 1,420 CALL SITES. Counted on nightly 2026-09-22 across the 328 modules under spacr/qt: about 1,420 hard-coded geometry calls in 181 files (470 setContentsMargins, 489 setSpacing, 179 setMinimumWidth/Height, 67 setMaximumWidth/Height, 74 setFixed*, 30 setMinimumSize, 56 QSize, 64 resize, 6 setIconSize) and 486 px literals in style-sheet text in 64 files. Routing each by hand is a change to every screen, and every size added later would have to remember it. Instead install_scaling_layer() – called once from spacr.qt.app.launch() before the first widget exists – replaces the setters themselves on the Qt classes (QWidget’s size setters and setStyleSheet, the layouts’ margin and spacing setters, every setIconSize, QSplitter.setSizes). Each replacement

  • remembers, on the object, the value the code asked for – its size at 100 %;

  • passes the value times the current scale on to Qt;

  • and answers the matching getter with the remembered value, so code that reads a size back and sets it again (the theme compares its own sheet by digest, a splitter saves sizes()) keeps working in 100 % units and never compounds.

A change of scale (set_gui_scale_live()) walks every live widget and layout, puts every remembered value back at the new scale, and asks the windows to lay out again. A widget built later is scaled as it is built. A splitter’s panes keep the room they have on screen – what shrinks is what is inside them – and sizes() answers in 100 % units, so the sizes slice B persists survive a change of scale.

HOW IT COMPOSES WITH FONT SCALE. Font scale stays where it is: it writes the font sizes into the theme’s style sheet. That sheet goes through the setStyleSheet replacement like any other, so a font size ends up as base x font scale x GUI scale. 50 % GUI at 200 % font is half-size widgets with text the usual size on screen.

A USER’S OWN QT_SCALE_FACTOR is not touched and multiplies on the outside, as Qt always has.

KEEP OR REVERT. Every change of GUI scale or font scale applies at once and then asks “Keep these settings?” (KeepOrRevertDialog), counting down 15 s; the countdown, Esc and closing the dialog all put the old values back. The dialog is exempt from the scale and sets its own text size, so it is readable at 10 % GUI and 10 % font alike. Ctrl+Alt+0 (reset_every_scale()) remains the backup.

WHAT DOES NOT FOLLOW LIVE. Sizes the scale cannot see because they do not pass through a setter: text and shapes a widget paints itself at fixed pixel coordinates, pixmaps a widget scales to a number it computed, setFont with an explicit size, move/setGeometry positions, header section sizes, a Python sizeHint override that returns a constant, the Fusion style’s own metrics that the theme sheet does not restate, and pyqtgraph’s axis text. Sizes computed from font metrics are already scaled once by the font and are scaled again by the setter – the one way the layer can over-shrink. The main window’s own size is left alone, so the gained room goes to the content.

Functions

add_listener(→ None)

Call callback(scale) after every change of GUI scale.

apply_canvas_dpi(→ bool)

Draw a matplotlib canvas at base dpi x GUI scale x its preview scale.

apply_saved_gui_scale(→ float)

Start at the saved GUI scale; called once the application exists.

change_scales([parent, gui, font, ask, seconds, ...])

Apply a new GUI and/or font scale now, then ask whether to keep it.

current_scale(→ float)

The GUI scale in force now (1.0 = 100 %).

follow_canvas(→ bool)

Draw a new matplotlib canvas at the current GUI scale.

install_scaling_layer(→ bool)

Put the scaling layer on the Qt classes. Idempotent.

installed(→ bool)

Whether install_scaling_layer() has run in this process.

keep_or_revert_dialog([parent, seconds, what])

A new KeepOrRevertDialog (see _dialog_classes()).

mend_matplotlib_icons(→ bool)

Keep matplotlib's toolbar icons full size below a device ratio of 1.

refresh_the_windows(→ int)

Rebuild what a style sheet cannot reach: icons, tiles, window chrome.

reset_every_scale(→ bool)

Put GUI scale, font scale and every preview scale back to 100 %, now.

scale_int(→ int)

value at factor, keeping 0, negatives and Qt's maximum as they are.

scale_qss_text(→ str)

Scale the sizes in a style sheet, leaving every other byte as it was.

set_gui_scale_live(→ float)

Draw spaCR at scale now: every live widget, and every one built later.

Module Contents

spacr.qt.gui_scale.add_listener(callback: Callable[[float], None]) → None[source]

Call callback(scale) after every change of GUI scale.

Held weakly when it is a bound method, so a listener does not keep its widget alive.

Parameters:

callback – called with the new scale.

spacr.qt.gui_scale.apply_canvas_dpi(canvas, factor: float | None = None) → bool[source]

Draw a matplotlib canvas at base dpi x GUI scale x its preview scale.

A figure draws in points, so its dpi is what makes a 9 pt label take more or fewer pixels; scaling the dpi scales every label, dot and line while the widget keeps the size its layout gives it.

Parameters:
  • canvas – a FigureCanvasQTAgg.

  • factor – the GUI scale; the current one by default.

Returns:

True if the canvas’s dpi was set.

spacr.qt.gui_scale.apply_saved_gui_scale() → float[source]

Start at the saved GUI scale; called once the application exists.

Returns:

the scale in force.

spacr.qt.gui_scale.change_scales(parent=None, *, gui: float | None = None, font: float | None = None, ask: bool = True, seconds: int = KEEP_SECONDS, previous: tuple | None = None, require_parent: bool = True, on_done: Callable[[bool], None] | None = None)[source]

Apply a new GUI and/or font scale now, then ask whether to keep it.

The question is shown without blocking (open, not exec): the rest of spaCR keeps running under it, and the countdown reverts by itself if nobody answers.

Parameters:
  • parent – the window the question is centred on.

  • gui – the new GUI scale; unchanged when None.

  • font – the new font scale; unchanged when None.

  • ask – False keeps without asking.

  • seconds – the countdown before it reverts by itself.

  • previous – (gui, font) to revert to when the caller has already applied and saved the new values (the Z + wheel gesture); nothing is applied again then.

  • require_parent – ask only when there is a window to centre the question on. False asks anyway, which is what a test does.

  • on_done – called with True when kept, False when reverted.

Returns:

the question dialog, or None when nothing was asked.

spacr.qt.gui_scale.current_scale() → float[source]

The GUI scale in force now (1.0 = 100 %).

spacr.qt.gui_scale.follow_canvas(canvas) → bool[source]

Draw a new matplotlib canvas at the current GUI scale.

Parameters:

canvas – a FigureCanvasQTAgg just built.

Returns:

True if its dpi was set.

spacr.qt.gui_scale.install_scaling_layer() → bool[source]

Put the scaling layer on the Qt classes. Idempotent.

Called from spacr.qt.app.launch() before the first widget, so every size spaCR sets is remembered at 100 %. At 100 % every replacement hands Qt exactly the value it was given, which is why a default session draws the pixels it drew before the layer existed.

Returns:

True the first time, False if it was already in place.

spacr.qt.gui_scale.installed() → bool[source]

Whether install_scaling_layer() has run in this process.

spacr.qt.gui_scale.keep_or_revert_dialog(parent=None, seconds: int = KEEP_SECONDS, what: str = '')[source]

A new KeepOrRevertDialog (see _dialog_classes()).

Parameters:
  • parent – the window it is centred on.

  • seconds – how long before it reverts by itself.

  • what – the line naming what changed.

spacr.qt.gui_scale.mend_matplotlib_icons() → bool[source]

Keep matplotlib’s toolbar icons full size below a device ratio of 1.

Matplotlib’s toolbar icon engine multiplies the size Qt asks for by the device pixel ratio, and Qt has already done so; above 1 the two cancel, below 1 (a user’s QT_SCALE_FACTOR under 1) they compound. Measured offscreen at a ratio of 0.5: the icons drew 3 px tall instead of 11. Holding the engine’s ratio at 1 or more draws them 11 px tall and changes nothing at a ratio of 1 or 2. Idempotent.

Returns:

True if the mend is in place.

spacr.qt.gui_scale.refresh_the_windows() → int[source]

Rebuild what a style sheet cannot reach: icons, tiles, window chrome.

The same step the Z + wheel gesture ends with. MainWindow.refresh_theme repaints the marks in the window corner and the Home tiles, which paint their own pixmaps at a size they compute – and a pixmap is not a size the scaling layer ever sees.

Returns:

how many windows rebuilt.

spacr.qt.gui_scale.reset_every_scale(parent=None) → bool[source]

Put GUI scale, font scale and every preview scale back to 100 %, now.

The backup way out of a scale too small to read: Ctrl+Alt+0. It needs no reading and no answer.

Parameters:

parent – unused; kept so a shortcut can pass its window.

Returns:

True.

spacr.qt.gui_scale.scale_int(value, factor: float | None = None) → int[source]

value at factor, keeping 0, negatives and Qt’s maximum as they are.

Parameters:
  • value – a size in 100 % pixels.

  • factor – the scale; the current one by default.

spacr.qt.gui_scale.scale_qss_text(text: str, factor: float | None = None) → str[source]

Scale the sizes in a style sheet, leaving every other byte as it was.

Only the values of size properties (font sizes, paddings, margins, min/max sizes, widths, heights, spacing, radii) change; selectors, colours, borders and url(...) data are untouched.

Parameters:
  • text – the style sheet.

  • factor – the scale; the current one by default.

spacr.qt.gui_scale.set_gui_scale_live(scale: float) → float[source]

Draw spaCR at scale now: every live widget, and every one built later.

Does not save the preference; see spacr.qt.preferences.set_gui_scale().

Parameters:

scale – the factor, 1.0 = 100 %; clamped to 10-200 %.

Returns:

the scale applied.

Nested helpers

_dialog_classes.KeepOrRevertDialog.__init__(self, parent=None, seconds: int = KEEP_SECONDS, what: str = '')

Build the question and start the countdown.

spacr/qt/gui_scale.py:973

_dialog_classes.KeepOrRevertDialog._show_left(self) → None

Say how long is left.

spacr/qt/gui_scale.py:1029

_dialog_classes.KeepOrRevertDialog._tick(self) → None

One second less; at zero, revert.

spacr/qt/gui_scale.py:1034

_dialog_classes.KeepOrRevertDialog.done(self, result)

Stop the countdown on any answer.

Parameters:

result – the dialog result.

spacr/qt/gui_scale.py:1043

_install_application_sheet.setStyleSheet(self, text)

Record the sheet at 100 % and apply its sizes at the GUI scale.

spacr/qt/gui_scale.py:623

_install_application_sheet.styleSheet(self)

The sheet as the code set it, at 100 %.

spacr/qt/gui_scale.py:630

_install_icon_setters.getter(original)

The wrapper that answers an icon size at 100 %.

spacr/qt/gui_scale.py:562

_install_icon_setters.getter.iconSize(self)

The icon size in 100 % units.

spacr/qt/gui_scale.py:564

_install_icon_setters.setter(original)

The wrapper that records an icon size and scales it.

spacr/qt/gui_scale.py:543

_install_icon_setters.setter.setIconSize(self, size)

Record the icon size at 100 % and apply it at the GUI scale.

spacr/qt/gui_scale.py:545

_install_layout_setters.addSpacing(self, size)

Add a spacer recorded at 100 % and drawn at the GUI scale.

spacr/qt/gui_scale.py:524

_install_layout_setters.insertSpacing(self, index, size)

Insert a spacer recorded at 100 % and drawn at the GUI scale.

spacr/qt/gui_scale.py:528

_install_layout_setters.margins_getter(original)

The wrapper that answers a layout’s margins at 100 %.

spacr/qt/gui_scale.py:446

_install_layout_setters.margins_getter.contentsMargins(self)

The margins in 100 % units.

spacr/qt/gui_scale.py:448

_install_layout_setters.margins_setter(original)

The wrapper that records a layout’s margins and scales them.

spacr/qt/gui_scale.py:432

_install_layout_setters.margins_setter.setContentsMargins(self, *args)

Record the margins at 100 % and apply them at the GUI scale.

spacr/qt/gui_scale.py:434

_install_layout_setters.spacer(self, index, size)

A fixed spacer item along the box’s direction.

spacr/qt/gui_scale.py:499

_install_layout_setters.spacing_getter(key)

The factory for one spacing getter, by its record key.

spacr/qt/gui_scale.py:474

_install_layout_setters.spacing_getter.make(original)

The wrapper that answers a spacing at 100 %.

spacr/qt/gui_scale.py:476

_install_layout_setters.spacing_getter.make.getter(self)

The spacing in 100 % units.

spacr/qt/gui_scale.py:478

_install_layout_setters.spacing_setter(key)

The factory for one spacing setter, by its record key.

spacr/qt/gui_scale.py:460

_install_layout_setters.spacing_setter.make(original)

The wrapper that records a spacing and scales it.

spacr/qt/gui_scale.py:462

_install_layout_setters.spacing_setter.make.setter(self, value)

Record the spacing at 100 % and apply it at the GUI scale.

spacr/qt/gui_scale.py:464

_install_splitter_setters.setSizes(self, sizes)

Apply 100 % sizes at the GUI scale, and remember them.

spacr/qt/gui_scale.py:591

_install_splitter_setters.sizes(self)

The sizes in 100 % units.

spacr/qt/gui_scale.py:601

_install_widget_setters.maximumHeight(self)

The maximum height in 100 % units.

spacr/qt/gui_scale.py:341

_install_widget_setters.maximumSize(self)

The maximum size in 100 % units.

spacr/qt/gui_scale.py:349

_install_widget_setters.maximumWidth(self)

The maximum width in 100 % units.

spacr/qt/gui_scale.py:337

_install_widget_setters.minimumHeight(self)

The minimum height in 100 % units.

spacr/qt/gui_scale.py:333

_install_widget_setters.minimumSize(self)

The minimum size in 100 % units.

spacr/qt/gui_scale.py:345

_install_widget_setters.minimumWidth(self)

The minimum width in 100 % units.

spacr/qt/gui_scale.py:329

_install_widget_setters.resize(self, *args)

Resize at the GUI scale; the main window keeps its size.

spacr/qt/gui_scale.py:355

_install_widget_setters.setFixedHeight(self, h)

Record h at 100 % and apply it at the GUI scale.

spacr/qt/gui_scale.py:320

_install_widget_setters.setFixedSize(self, *args)

Record the size at 100 % and apply it at the GUI scale.

spacr/qt/gui_scale.py:324

_install_widget_setters.setFixedWidth(self, w)

Record w at 100 % and apply it at the GUI scale.

spacr/qt/gui_scale.py:316

_install_widget_setters.setMaximumHeight(self, h)

Record h at 100 % and apply it at the GUI scale.

spacr/qt/gui_scale.py:302

_install_widget_setters.setMaximumSize(self, *args)

Record the size at 100 % and apply it at the GUI scale.

spacr/qt/gui_scale.py:311

_install_widget_setters.setMaximumWidth(self, w)

Record w at 100 % and apply it at the GUI scale.

spacr/qt/gui_scale.py:298

_install_widget_setters.setMinimumHeight(self, h)

Record h at 100 % and apply it at the GUI scale.

spacr/qt/gui_scale.py:294

_install_widget_setters.setMinimumSize(self, *args)

Record the size at 100 % and apply it at the GUI scale.

spacr/qt/gui_scale.py:306

_install_widget_setters.setMinimumWidth(self, w)

Record w at 100 % and apply it at the GUI scale.

spacr/qt/gui_scale.py:290

_install_widget_setters.setStyleSheet(self, text)

Record the sheet at 100 % and apply its sizes at the GUI scale.

spacr/qt/gui_scale.py:364

_install_widget_setters.styleSheet(self)

The sheet as the code set it, at 100 %.

spacr/qt/gui_scale.py:377

change_scales._answered(result) → None

Keep, or put the old values back.

spacr/qt/gui_scale.py:1139

mend_matplotlib_icons._at_least_one(self)

The toolbar’s device ratio, never below 1.

spacr/qt/gui_scale.py:1229