Add `get_godw()` singleton getter for `GodWidget`

Expose `get_godw()` helper to retrieve the central `GodWidget`
instance from anywhere in the UI code. Set the singleton in
`_async_main()` on startup.

Also,
- add docstring to `run_qtractor()` explaining trio guest mode
- type annotate `instance: GodWidget` in `run_qtractor()`
- import reorg in `._app` for cleaner grouping
- whitespace cleanup: `Type | None` -> `Type|None` throughout
- fix bitwise-or alignment: `Flag | Other` -> `Flag|Other`

(this commit-msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
refresh_annots
Gud Boi 2026-01-30 15:39:25 -05:00
parent eb78437994
commit bac8317a4a
4 changed files with 72 additions and 46 deletions

View File

@ -27,15 +27,15 @@ import trio
from piker.ui.qt import ( from piker.ui.qt import (
QEvent, QEvent,
) )
from ..service import maybe_spawn_brokerd from . import _chart
from . import _event from . import _event
from ._exec import run_qtractor
from ..data.feed import install_brokerd_search
from ..data._symcache import open_symcache
from ..accounting import unpack_fqme
from . import _search from . import _search
from ._chart import GodWidget from ..accounting import unpack_fqme
from ..data._symcache import open_symcache
from ..data.feed import install_brokerd_search
from ..log import get_logger from ..log import get_logger
from ..service import maybe_spawn_brokerd
from ._exec import run_qtractor
log = get_logger(__name__) log = get_logger(__name__)
@ -73,8 +73,8 @@ async def load_provider_search(
async def _async_main( async def _async_main(
# implicit required argument provided by ``qtractor_run()`` # implicit required argument provided by `qtractor_run()`
main_widget: GodWidget, main_widget: _chart.GodWidget,
syms: list[str], syms: list[str],
brokers: dict[str, ModuleType], brokers: dict[str, ModuleType],
@ -87,6 +87,9 @@ async def _async_main(
Provision the "main" widget with initial symbol data and root nursery. Provision the "main" widget with initial symbol data and root nursery.
""" """
# set as singleton
_chart._godw = main_widget
from . import _display from . import _display
from ._pg_overrides import _do_overrides from ._pg_overrides import _do_overrides
_do_overrides() _do_overrides()
@ -201,6 +204,6 @@ def _main(
brokermods, brokermods,
piker_loglevel, piker_loglevel,
), ),
main_widget_type=GodWidget, main_widget_type=_chart.GodWidget,
tractor_kwargs=tractor_kwargs, tractor_kwargs=tractor_kwargs,
) )

View File

@ -82,6 +82,25 @@ if TYPE_CHECKING:
log = get_logger(__name__) log = get_logger(__name__)
_godw: GodWidget|None = None
def get_godw() -> GodWidget:
'''
Get the top level "god widget", the root/central-most Qt
widget-object set as `QMainWindow.setCentralWidget(_godw)`.
See `piker.ui._exec` for the runtime init details and all the
machinery for running `trio` on the Qt event loop in guest mode.
'''
if _godw is None:
raise RuntimeError(
'No god-widget initialized ??\n'
'Have you called `run_qtractor()` yet?\n'
)
return _godw
class GodWidget(QWidget): class GodWidget(QWidget):
''' '''
"Our lord and savior, the holy child of window-shua, there is no "Our lord and savior, the holy child of window-shua, there is no
@ -104,7 +123,7 @@ class GodWidget(QWidget):
super().__init__(parent) super().__init__(parent)
self.search: SearchWidget | None = None self.search: SearchWidget|None = None
self.hbox = QHBoxLayout(self) self.hbox = QHBoxLayout(self)
self.hbox.setContentsMargins(0, 0, 0, 0) self.hbox.setContentsMargins(0, 0, 0, 0)
@ -123,9 +142,9 @@ class GodWidget(QWidget):
tuple[LinkedSplits, LinkedSplits], tuple[LinkedSplits, LinkedSplits],
] = {} ] = {}
self.hist_linked: LinkedSplits | None = None self.hist_linked: LinkedSplits|None = None
self.rt_linked: LinkedSplits | None = None self.rt_linked: LinkedSplits|None = None
self._active_cursor: Cursor | None = None self._active_cursor: Cursor|None = None
# assigned in the startup func `_async_main()` # assigned in the startup func `_async_main()`
self._root_n: trio.Nursery = None self._root_n: trio.Nursery = None
@ -369,9 +388,9 @@ class ChartnPane(QFrame):
https://doc.qt.io/qt-5/qwidget.html#composite-widgets https://doc.qt.io/qt-5/qwidget.html#composite-widgets
''' '''
sidepane: FieldsForm | SearchWidget sidepane: FieldsForm|SearchWidget
hbox: QHBoxLayout hbox: QHBoxLayout
chart: ChartPlotWidget | None = None chart: ChartPlotWidget|None = None
def __init__( def __init__(
self, self,
@ -387,13 +406,13 @@ class ChartnPane(QFrame):
self.chart = None self.chart = None
hbox = self.hbox = QHBoxLayout(self) hbox = self.hbox = QHBoxLayout(self)
hbox.setAlignment(Qt.AlignTop | Qt.AlignLeft) hbox.setAlignment(Qt.AlignTop|Qt.AlignLeft)
hbox.setContentsMargins(0, 0, 0, 0) hbox.setContentsMargins(0, 0, 0, 0)
hbox.setSpacing(3) hbox.setSpacing(3)
def set_sidepane( def set_sidepane(
self, self,
sidepane: FieldsForm | SearchWidget, sidepane: FieldsForm|SearchWidget,
) -> None: ) -> None:
# add sidepane **after** chart; place it on axis side # add sidepane **after** chart; place it on axis side
@ -404,7 +423,7 @@ class ChartnPane(QFrame):
self._sidepane = sidepane self._sidepane = sidepane
@property @property
def sidepane(self) -> FieldsForm | SearchWidget: def sidepane(self) -> FieldsForm|SearchWidget:
return self._sidepane return self._sidepane
@ -450,7 +469,7 @@ class LinkedSplits(QWidget):
# chart-local graphics state that can be passed to # chart-local graphics state that can be passed to
# a ``graphic_update_cycle()`` call by any task wishing to # a ``graphic_update_cycle()`` call by any task wishing to
# update the UI for a given "chart instance". # update the UI for a given "chart instance".
self.display_state: DisplayState | None = None self.display_state: DisplayState|None = None
self._mkt: MktPair = None self._mkt: MktPair = None
@ -486,7 +505,7 @@ class LinkedSplits(QWidget):
def set_split_sizes( def set_split_sizes(
self, self,
prop: float | None = None, prop: float|None = None,
) -> None: ) -> None:
''' '''
@ -567,8 +586,8 @@ class LinkedSplits(QWidget):
# style? # style?
self.chart.setFrameStyle( self.chart.setFrameStyle(
QFrame.Shape.StyledPanel | QFrame.Shape.StyledPanel
QFrame.Shadow.Plain |QFrame.Shadow.Plain
) )
return self.chart return self.chart
@ -580,11 +599,11 @@ class LinkedSplits(QWidget):
shm: ShmArray, shm: ShmArray,
flume: Flume, flume: Flume,
array_key: str | None = None, array_key: str|None = None,
style: str = 'line', style: str = 'line',
_is_main: bool = False, _is_main: bool = False,
sidepane: QWidget | None = None, sidepane: QWidget|None = None,
draw_kwargs: dict = {}, draw_kwargs: dict = {},
**cpw_kwargs, **cpw_kwargs,
@ -687,7 +706,7 @@ class LinkedSplits(QWidget):
cpw.plotItem.vb.linked = self cpw.plotItem.vb.linked = self
cpw.setFrameStyle( cpw.setFrameStyle(
QFrame.Shape.StyledPanel QFrame.Shape.StyledPanel
# | QFrame.Shadow.Plain # |QFrame.Shadow.Plain
) )
# don't show the little "autoscale" A label. # don't show the little "autoscale" A label.
@ -800,7 +819,7 @@ class LinkedSplits(QWidget):
def resize_sidepanes( def resize_sidepanes(
self, self,
from_linked: LinkedSplits | None = None, from_linked: LinkedSplits|None = None,
) -> None: ) -> None:
''' '''
@ -874,7 +893,7 @@ class ChartPlotWidget(pg.PlotWidget):
# TODO: load from config # TODO: load from config
use_open_gl: bool = False, use_open_gl: bool = False,
static_yrange: tuple[float, float] | None = None, static_yrange: tuple[float, float]|None = None,
parent=None, parent=None,
**kwargs, **kwargs,
@ -889,7 +908,7 @@ class ChartPlotWidget(pg.PlotWidget):
# NOTE: must be set bfore calling ``.mk_vb()`` # NOTE: must be set bfore calling ``.mk_vb()``
self.linked = linkedsplits self.linked = linkedsplits
self.sidepane: FieldsForm | None = None self.sidepane: FieldsForm|None = None
# source of our custom interactions # source of our custom interactions
self.cv = self.mk_vb(name) self.cv = self.mk_vb(name)
@ -923,7 +942,7 @@ class ChartPlotWidget(pg.PlotWidget):
self.useOpenGL(use_open_gl) self.useOpenGL(use_open_gl)
self.name = name self.name = name
self.data_key = data_key or name self.data_key = data_key or name
self.qframe: ChartnPane | None = None self.qframe: ChartnPane|None = None
# scene-local placeholder for book graphics # scene-local placeholder for book graphics
# sizing to avoid overlap with data contents # sizing to avoid overlap with data contents
@ -934,7 +953,7 @@ class ChartPlotWidget(pg.PlotWidget):
# registry of overlay curve names # registry of overlay curve names
self._vizs: dict[str, Viz] = {} self._vizs: dict[str, Viz] = {}
self.feed: Feed | None = None self.feed: Feed|None = None
self._labels = {} # registry of underlying graphics self._labels = {} # registry of underlying graphics
self._ysticks = {} # registry of underlying graphics self._ysticks = {} # registry of underlying graphics
@ -1027,7 +1046,7 @@ class ChartPlotWidget(pg.PlotWidget):
def increment_view( def increment_view(
self, self,
datums: int = 1, datums: int = 1,
vb: ChartView | None = None, vb: ChartView|None = None,
) -> None: ) -> None:
''' '''
@ -1058,8 +1077,8 @@ class ChartPlotWidget(pg.PlotWidget):
def overlay_plotitem( def overlay_plotitem(
self, self,
name: str, name: str,
index: int | None = None, index: int|None = None,
axis_title: str | None = None, axis_title: str|None = None,
axis_side: str = 'right', axis_side: str = 'right',
axis_kwargs: dict = {}, axis_kwargs: dict = {},
@ -1147,14 +1166,14 @@ class ChartPlotWidget(pg.PlotWidget):
shm: ShmArray, shm: ShmArray,
flume: Flume, flume: Flume,
array_key: str | None = None, array_key: str|None = None,
overlay: bool = False, overlay: bool = False,
color: str | None = None, color: str|None = None,
add_label: bool = True, add_label: bool = True,
pi: pg.PlotItem | None = None, pi: pg.PlotItem|None = None,
step_mode: bool = False, step_mode: bool = False,
is_ohlc: bool = False, is_ohlc: bool = False,
add_sticky: None | str = 'right', add_sticky: None|str = 'right',
**graphics_kwargs, **graphics_kwargs,
@ -1252,7 +1271,7 @@ class ChartPlotWidget(pg.PlotWidget):
# use the tick size precision for display # use the tick size precision for display
name = name or pi.name name = name or pi.name
mkt: MktPair = self.linked.mkt mkt: MktPair = self.linked.mkt
digits: int | None = None digits: int|None = None
if name in mkt.fqme: if name in mkt.fqme:
digits = mkt.price_tick_digits digits = mkt.price_tick_digits
@ -1286,7 +1305,7 @@ class ChartPlotWidget(pg.PlotWidget):
shm: ShmArray, shm: ShmArray,
flume: Flume, flume: Flume,
array_key: str | None = None, array_key: str|None = None,
**draw_curve_kwargs, **draw_curve_kwargs,
) -> Viz: ) -> Viz:

View File

@ -91,6 +91,10 @@ def run_qtractor(
window_type: QMainWindow = None, window_type: QMainWindow = None,
) -> None: ) -> None:
'''
Run the Qt event loop and embed `trio` via guest mode on it.
'''
# avoids annoying message when entering debugger from qt loop # avoids annoying message when entering debugger from qt loop
pyqtRemoveInputHook() pyqtRemoveInputHook()
@ -170,7 +174,7 @@ def run_qtractor(
# hook into app focus change events # hook into app focus change events
app.focusChanged.connect(window.on_focus_change) app.focusChanged.connect(window.on_focus_change)
instance = main_widget_type() instance: GodWidget = main_widget_type()
instance.window = window instance.window = window
# override tractor's defaults # override tractor's defaults

View File

@ -61,9 +61,9 @@ class MultiStatus:
self, self,
msg: str, msg: str,
final_msg: str | None = None, final_msg: str|None = None,
clear_on_next: bool = False, clear_on_next: bool = False,
group_key: Union[bool, str] | None = False, group_key: Union[bool, str]|None = False,
) -> Union[Callable[..., None], str]: ) -> Union[Callable[..., None], str]:
''' '''
@ -175,11 +175,11 @@ class MainWindow(QMainWindow):
self.setWindowTitle(self.title) self.setWindowTitle(self.title)
# set by runtime after `trio` is engaged. # set by runtime after `trio` is engaged.
self.godwidget: GodWidget | None = None self.godwidget: GodWidget|None = None
self._status_bar: QStatusBar = None self._status_bar: QStatusBar = None
self._status_label: QLabel = None self._status_label: QLabel = None
self._size: tuple[int, int] | None = None self._size: tuple[int, int]|None = None
@property @property
def mode_label(self) -> QLabel: def mode_label(self) -> QLabel:
@ -202,7 +202,7 @@ class MainWindow(QMainWindow):
label.setMargin(2) label.setMargin(2)
label.setAlignment( label.setAlignment(
QtCore.Qt.AlignVCenter QtCore.Qt.AlignVCenter
| QtCore.Qt.AlignRight |QtCore.Qt.AlignRight
) )
self.statusBar().addPermanentWidget(label) self.statusBar().addPermanentWidget(label)
label.show() label.show()
@ -288,7 +288,7 @@ class MainWindow(QMainWindow):
def configure_to_desktop( def configure_to_desktop(
self, self,
size: tuple[int, int] | None = None, size: tuple[int, int]|None = None,
) -> None: ) -> None:
''' '''