From 40a97619432c709e8c2e37c222e5a084efe7288e Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Thu, 8 Sep 2022 20:00:50 -0400 Subject: [PATCH] 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()`. --- piker/ui/_app.py | 2 +- piker/ui/_chart.py | 62 ++++++++++++++++++++++++++++++++++----------- piker/ui/_exec.py | 20 +++++++++------ piker/ui/_window.py | 23 ++++++++++++++--- 4 files changed, 79 insertions(+), 28 deletions(-) diff --git a/piker/ui/_app.py b/piker/ui/_app.py index 3fa9d1b4..c99e2866 100644 --- a/piker/ui/_app.py +++ b/piker/ui/_app.py @@ -177,6 +177,6 @@ def _main( run_qtractor( func=_async_main, args=(sym, brokernames, piker_loglevel), - main_widget=GodWidget, + main_widget_type=GodWidget, tractor_kwargs=tractor_kwargs, ) diff --git a/piker/ui/_chart.py b/piker/ui/_chart.py index dd52699f..e03f7fe0 100644 --- a/piker/ui/_chart.py +++ b/piker/ui/_chart.py @@ -91,6 +91,7 @@ class GodWidget(QWidget): ''' search: SearchWidget + mode_name: str = 'god' def __init__( @@ -135,6 +136,10 @@ class GodWidget(QWidget): self._widgets: dict[str, QWidget] = {} 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 def linkedsplits(self) -> LinkedSplits: return self.rt_linked @@ -203,11 +208,14 @@ class GodWidget(QWidget): 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]: # XXX: this is CRITICAL especially with pixel buffer caching linked.hide() linked.unfocus() - # self.hist_linked.hide() # self.hist_linked.unfocus() # XXX: pretty sure we don't need this @@ -244,7 +252,6 @@ class GodWidget(QWidget): linked.show() linked.focus() - self.search.focus() await trio.sleep(0) else: @@ -279,6 +286,17 @@ class GodWidget(QWidget): # do nothing yah? 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 symbol = self.rt_linked.symbol if symbol is not None: @@ -297,11 +315,23 @@ class GodWidget(QWidget): ''' # go back to view-mode focus (aka chart focus) 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) @@ -317,6 +347,8 @@ class GodWidget(QWidget): self._resizing = False + # on_resize = on_win_resize + def get_cursor(self) -> Cursor: return self._active_cursor @@ -336,9 +368,9 @@ class ChartnPane(QFrame): https://doc.qt.io/qt-5/qwidget.html#composite-widgets ''' - sidepane: FieldsForm + sidepane: FieldsForm | SearchWidget hbox: QHBoxLayout - chart: Optional['ChartPlotWidget'] = None + chart: Optional[ChartPlotWidget] = None def __init__( self, @@ -350,7 +382,7 @@ class ChartnPane(QFrame): super().__init__(parent) - self.sidepane = sidepane + self._sidepane = sidepane self.chart = None hbox = self.hbox = QHBoxLayout(self) @@ -360,7 +392,7 @@ class ChartnPane(QFrame): def set_sidepane( self, - sidepane: FieldsForm, + sidepane: FieldsForm | SearchWidget, ) -> None: # add sidepane **after** chart; place it on axis side @@ -368,6 +400,10 @@ class ChartnPane(QFrame): sidepane, alignment=Qt.AlignTop ) + self._sidepane = sidepane + + def sidepane(self) -> FieldsForm | SearchWidget: + return self._sidepane class LinkedSplits(QWidget): @@ -672,9 +708,6 @@ class LinkedSplits(QWidget): if qframe is not None: self.splitter.addWidget(qframe) - # scale split regions - self.set_split_sizes() - else: assert style == 'bar', 'main chart must be OHLC' @@ -694,6 +727,8 @@ class LinkedSplits(QWidget): anchor_at=anchor_at, ) + # scale split regions + self.set_split_sizes() self.resize_sidepanes() return cpw @@ -720,9 +755,6 @@ class LinkedSplits(QWidget): if from_linked: 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): diff --git a/piker/ui/_exec.py b/piker/ui/_exec.py index 1d1a9c3d..090b783a 100644 --- a/piker/ui/_exec.py +++ b/piker/ui/_exec.py @@ -20,13 +20,16 @@ Trio - Qt integration Run ``trio`` in guest mode on top of the Qt event loop. 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 traceback # Qt specific import PyQt5 # noqa -import pyqtgraph as pg from pyqtgraph import QtGui from PyQt5 import QtCore # from PyQt5.QtGui import QLabel, QStatusBar @@ -37,7 +40,7 @@ from PyQt5.QtCore import ( ) import qdarkstyle from qdarkstyle import DarkPalette -# import qdarkgraystyle +# import qdarkgraystyle # TODO: play with it import trio from outcome import Error @@ -72,10 +75,11 @@ if platform.system() == "Windows": def run_qtractor( func: Callable, - args: Tuple, - main_widget: QtGui.QWidget, - tractor_kwargs: Dict[str, Any] = {}, + args: tuple, + main_widget_type: Type[QtGui.QWidget], + tractor_kwargs: dict[str, Any] = {}, window_type: QtGui.QMainWindow = None, + ) -> None: # avoids annoying message when entering debugger from qt loop pyqtRemoveInputHook() @@ -156,7 +160,7 @@ def run_qtractor( # hook into app focus change events app.focusChanged.connect(window.on_focus_change) - instance = main_widget() + instance = main_widget_type() instance.window = window # override tractor's defaults @@ -178,7 +182,7 @@ def run_qtractor( # restrict_keyboard_interrupt_to_checkpoints=True, ) - window.main_widget = main_widget + window.godwidget: GodWidget = instance window.setCentralWidget(instance) if is_windows: window.configure_to_desktop() diff --git a/piker/ui/_window.py b/piker/ui/_window.py index 6a39b0c5..5d720c0b 100644 --- a/piker/ui/_window.py +++ b/piker/ui/_window.py @@ -21,7 +21,11 @@ Qt main window singletons and stuff. import os import signal import time -from typing import Callable, Optional, Union +from typing import ( + Callable, + Optional, + Union, +) import uuid from pyqtgraph import QtGui @@ -30,6 +34,7 @@ from PyQt5.QtWidgets import QLabel, QStatusBar from ..log import get_logger from ._style import _font_small, hcolor +from ._chart import GodWidget log = get_logger(__name__) @@ -154,6 +159,7 @@ class MainWindow(QtGui.QMainWindow): # with the alloted window size. # TODO: detect for tiling and if untrue set some size? size = (300, 500) + godwidget: GodWidget title = 'piker chart (ur symbol is loading bby)' @@ -162,6 +168,9 @@ class MainWindow(QtGui.QMainWindow): # self.setMinimumSize(*self.size) self.setWindowTitle(self.title) + # set by runtime after `trio` is engaged. + self.godwidget: Optional[GodWidget] = None + self._status_bar: QStatusBar = None self._status_label: QLabel = None self._size: Optional[tuple[int, int]] = None @@ -248,9 +257,10 @@ class MainWindow(QtGui.QMainWindow): self.set_mode_name(name) 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() for _ in range(3): @@ -284,7 +294,7 @@ class MainWindow(QtGui.QMainWindow): ''' # https://stackoverflow.com/a/18975846 if not size and not self._size: - app = QtGui.QApplication.instance() + # app = QtGui.QApplication.instance() geo = self.current_screen().geometry() h, w = geo.height(), geo.width() # 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) + def resizeEvent(self, event: QtCore.QEvent) -> None: + print('window resize') + # self.godwidget.resizeEvent(event) + self.godwidget.on_win_resize(event) + # singleton app per actor _qt_win: QtGui.QMainWindow = None