Actually support resize events..

Turns out god widget resizes aren't triggered implicitly by window
resizes, so instead, hook into the window by moving what was our useless
method to that class. Further we explicitly define and declare that our
window has a `.godwidget: GodWidget` and set it up in the bootstrap
phase - in `run_qutractor()` during `trio` guest mode configuration.

Further deatz:
- retype the runtime/bootstrap routines to take a qwidget "type" not an
  instance, and drop the whole implicit `.main_widget` stuff.
- delegate into the `GodWidget.on_win_resize()` for any window resize
  which then triggers all the custom resize callbacks we already had in
  place.
- privatize `ChartnPane.sidepane` so that it can't be mutated willy
  nilly without calling `.set_sidepane()`.
- always adjust splitter sizes inside `LinkeSplits.add_plot()`.
history_view
Tyler Goodlet 2022-09-08 20:00:50 -04:00
parent 256bcf36d3
commit 40a9761943
4 changed files with 79 additions and 28 deletions

View File

@ -177,6 +177,6 @@ def _main(
run_qtractor( run_qtractor(
func=_async_main, func=_async_main,
args=(sym, brokernames, piker_loglevel), args=(sym, brokernames, piker_loglevel),
main_widget=GodWidget, main_widget_type=GodWidget,
tractor_kwargs=tractor_kwargs, tractor_kwargs=tractor_kwargs,
) )

View File

@ -91,6 +91,7 @@ class GodWidget(QWidget):
''' '''
search: SearchWidget search: SearchWidget
mode_name: str = 'god'
def __init__( def __init__(
@ -135,6 +136,10 @@ class GodWidget(QWidget):
self._widgets: dict[str, QWidget] = {} self._widgets: dict[str, QWidget] = {}
self._resizing: bool = False self._resizing: bool = False
# TODO: do we need this, when would god get resized
# and the window does not? Never right?!
# self.reg_for_resize(self)
@property @property
def linkedsplits(self) -> LinkedSplits: def linkedsplits(self) -> LinkedSplits:
return self.rt_linked return self.rt_linked
@ -203,11 +208,14 @@ class GodWidget(QWidget):
if not self.vbox.isEmpty(): if not self.vbox.isEmpty():
qframe = self.hist_linked.chart.qframe
if qframe.sidepane is self.search:
qframe.hbox.removeWidget(self.search)
for linked in [self.rt_linked, self.hist_linked]: for linked in [self.rt_linked, self.hist_linked]:
# XXX: this is CRITICAL especially with pixel buffer caching # XXX: this is CRITICAL especially with pixel buffer caching
linked.hide() linked.hide()
linked.unfocus() linked.unfocus()
# self.hist_linked.hide()
# self.hist_linked.unfocus() # self.hist_linked.unfocus()
# XXX: pretty sure we don't need this # XXX: pretty sure we don't need this
@ -244,7 +252,6 @@ class GodWidget(QWidget):
linked.show() linked.show()
linked.focus() linked.focus()
self.search.focus()
await trio.sleep(0) await trio.sleep(0)
else: else:
@ -279,6 +286,17 @@ class GodWidget(QWidget):
# do nothing yah? # do nothing yah?
chart.default_view() chart.default_view()
# if a history chart instance is already up then
# set the search widget as its sidepane.
hist_chart = self.hist_linked.chart
if hist_chart:
hist_chart.qframe.set_sidepane(self.search)
# NOTE: this resizes the fast chart as well as all it's
# downstream fsp subcharts AND the slow chart which is part of
# the same splitter.
self.rt_linked.set_split_sizes()
# set window titlebar info # set window titlebar info
symbol = self.rt_linked.symbol symbol = self.rt_linked.symbol
if symbol is not None: if symbol is not None:
@ -297,11 +315,23 @@ class GodWidget(QWidget):
''' '''
# go back to view-mode focus (aka chart focus) # go back to view-mode focus (aka chart focus)
self.clearFocus() self.clearFocus()
self.linkedsplits.chart.setFocus() chart = self.rt_linked.chart
if chart:
chart.setFocus()
def resizeEvent(self, event: QtCore.QEvent) -> None: def reg_for_resize(
self,
widget: QWidget,
) -> None:
getattr(widget, 'on_resize')
self._widgets[widget.mode_name] = widget
def on_win_resize(self, event: QtCore.QEvent) -> None:
''' '''
Top level god widget resize handler. Top level god widget handler from window (the real yaweh) resize
events such that any registered widgets which wish to be
notified are invoked using our pythonic `.on_resize()` method
api.
Where we do UX magic to make things not suck B) Where we do UX magic to make things not suck B)
@ -317,6 +347,8 @@ class GodWidget(QWidget):
self._resizing = False self._resizing = False
# on_resize = on_win_resize
def get_cursor(self) -> Cursor: def get_cursor(self) -> Cursor:
return self._active_cursor return self._active_cursor
@ -336,9 +368,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 sidepane: FieldsForm | SearchWidget
hbox: QHBoxLayout hbox: QHBoxLayout
chart: Optional['ChartPlotWidget'] = None chart: Optional[ChartPlotWidget] = None
def __init__( def __init__(
self, self,
@ -350,7 +382,7 @@ class ChartnPane(QFrame):
super().__init__(parent) super().__init__(parent)
self.sidepane = sidepane self._sidepane = sidepane
self.chart = None self.chart = None
hbox = self.hbox = QHBoxLayout(self) hbox = self.hbox = QHBoxLayout(self)
@ -360,7 +392,7 @@ class ChartnPane(QFrame):
def set_sidepane( def set_sidepane(
self, self,
sidepane: FieldsForm, sidepane: FieldsForm | SearchWidget,
) -> None: ) -> None:
# add sidepane **after** chart; place it on axis side # add sidepane **after** chart; place it on axis side
@ -368,6 +400,10 @@ class ChartnPane(QFrame):
sidepane, sidepane,
alignment=Qt.AlignTop alignment=Qt.AlignTop
) )
self._sidepane = sidepane
def sidepane(self) -> FieldsForm | SearchWidget:
return self._sidepane
class LinkedSplits(QWidget): class LinkedSplits(QWidget):
@ -672,9 +708,6 @@ class LinkedSplits(QWidget):
if qframe is not None: if qframe is not None:
self.splitter.addWidget(qframe) self.splitter.addWidget(qframe)
# scale split regions
self.set_split_sizes()
else: else:
assert style == 'bar', 'main chart must be OHLC' assert style == 'bar', 'main chart must be OHLC'
@ -694,6 +727,8 @@ class LinkedSplits(QWidget):
anchor_at=anchor_at, anchor_at=anchor_at,
) )
# scale split regions
self.set_split_sizes()
self.resize_sidepanes() self.resize_sidepanes()
return cpw return cpw
@ -720,9 +755,6 @@ class LinkedSplits(QWidget):
if from_linked: if from_linked:
self.chart.sidepane.setMinimumWidth(sp_w) self.chart.sidepane.setMinimumWidth(sp_w)
self.chart.sidepane.setMaximumWidth(sp_w)
else:
self.godwidget.hist_linked.resize_sidepanes(from_linked=self)
class ChartPlotWidget(pg.PlotWidget): class ChartPlotWidget(pg.PlotWidget):

View File

@ -20,13 +20,16 @@ Trio - Qt integration
Run ``trio`` in guest mode on top of the Qt event loop. Run ``trio`` in guest mode on top of the Qt event loop.
All global Qt runtime settings are mostly defined here. All global Qt runtime settings are mostly defined here.
""" """
from typing import Tuple, Callable, Dict, Any from typing import (
Callable,
Any,
Type,
)
import platform import platform
import traceback import traceback
# Qt specific # Qt specific
import PyQt5 # noqa import PyQt5 # noqa
import pyqtgraph as pg
from pyqtgraph import QtGui from pyqtgraph import QtGui
from PyQt5 import QtCore from PyQt5 import QtCore
# from PyQt5.QtGui import QLabel, QStatusBar # from PyQt5.QtGui import QLabel, QStatusBar
@ -37,7 +40,7 @@ from PyQt5.QtCore import (
) )
import qdarkstyle import qdarkstyle
from qdarkstyle import DarkPalette from qdarkstyle import DarkPalette
# import qdarkgraystyle # import qdarkgraystyle # TODO: play with it
import trio import trio
from outcome import Error from outcome import Error
@ -72,10 +75,11 @@ if platform.system() == "Windows":
def run_qtractor( def run_qtractor(
func: Callable, func: Callable,
args: Tuple, args: tuple,
main_widget: QtGui.QWidget, main_widget_type: Type[QtGui.QWidget],
tractor_kwargs: Dict[str, Any] = {}, tractor_kwargs: dict[str, Any] = {},
window_type: QtGui.QMainWindow = None, window_type: QtGui.QMainWindow = None,
) -> None: ) -> None:
# avoids annoying message when entering debugger from qt loop # avoids annoying message when entering debugger from qt loop
pyqtRemoveInputHook() pyqtRemoveInputHook()
@ -156,7 +160,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() instance = main_widget_type()
instance.window = window instance.window = window
# override tractor's defaults # override tractor's defaults
@ -178,7 +182,7 @@ def run_qtractor(
# restrict_keyboard_interrupt_to_checkpoints=True, # restrict_keyboard_interrupt_to_checkpoints=True,
) )
window.main_widget = main_widget window.godwidget: GodWidget = instance
window.setCentralWidget(instance) window.setCentralWidget(instance)
if is_windows: if is_windows:
window.configure_to_desktop() window.configure_to_desktop()

View File

@ -21,7 +21,11 @@ Qt main window singletons and stuff.
import os import os
import signal import signal
import time import time
from typing import Callable, Optional, Union from typing import (
Callable,
Optional,
Union,
)
import uuid import uuid
from pyqtgraph import QtGui from pyqtgraph import QtGui
@ -30,6 +34,7 @@ from PyQt5.QtWidgets import QLabel, QStatusBar
from ..log import get_logger from ..log import get_logger
from ._style import _font_small, hcolor from ._style import _font_small, hcolor
from ._chart import GodWidget
log = get_logger(__name__) log = get_logger(__name__)
@ -154,6 +159,7 @@ class MainWindow(QtGui.QMainWindow):
# with the alloted window size. # with the alloted window size.
# TODO: detect for tiling and if untrue set some size? # TODO: detect for tiling and if untrue set some size?
size = (300, 500) size = (300, 500)
godwidget: GodWidget
title = 'piker chart (ur symbol is loading bby)' title = 'piker chart (ur symbol is loading bby)'
@ -162,6 +168,9 @@ class MainWindow(QtGui.QMainWindow):
# self.setMinimumSize(*self.size) # self.setMinimumSize(*self.size)
self.setWindowTitle(self.title) self.setWindowTitle(self.title)
# set by runtime after `trio` is engaged.
self.godwidget: Optional[GodWidget] = None
self._status_bar: QStatusBar = None self._status_bar: QStatusBar = None
self._status_label: QLabel = None self._status_label: QLabel = None
self._size: Optional[tuple[int, int]] = None self._size: Optional[tuple[int, int]] = None
@ -248,9 +257,10 @@ class MainWindow(QtGui.QMainWindow):
self.set_mode_name(name) self.set_mode_name(name)
def current_screen(self) -> QtGui.QScreen: def current_screen(self) -> QtGui.QScreen:
"""Get a frickin screen (if we can, gawd). '''
Get a frickin screen (if we can, gawd).
""" '''
app = QtGui.QApplication.instance() app = QtGui.QApplication.instance()
for _ in range(3): for _ in range(3):
@ -284,7 +294,7 @@ class MainWindow(QtGui.QMainWindow):
''' '''
# https://stackoverflow.com/a/18975846 # https://stackoverflow.com/a/18975846
if not size and not self._size: if not size and not self._size:
app = QtGui.QApplication.instance() # app = QtGui.QApplication.instance()
geo = self.current_screen().geometry() geo = self.current_screen().geometry()
h, w = geo.height(), geo.width() h, w = geo.height(), geo.width()
# use approx 1/3 of the area of the screen by default # use approx 1/3 of the area of the screen by default
@ -292,6 +302,11 @@ class MainWindow(QtGui.QMainWindow):
self.resize(*size or self._size) self.resize(*size or self._size)
def resizeEvent(self, event: QtCore.QEvent) -> None:
print('window resize')
# self.godwidget.resizeEvent(event)
self.godwidget.on_win_resize(event)
# singleton app per actor # singleton app per actor
_qt_win: QtGui.QMainWindow = None _qt_win: QtGui.QMainWindow = None