Compare commits
1 Commits
310_plus
...
window_cuc
Author | SHA1 | Date |
---|---|---|
Tyler Goodlet | fcf12b81b6 |
|
@ -52,6 +52,7 @@ from ._l1 import L1Labels
|
|||
from ._graphics._ohlc import BarItems
|
||||
from ._graphics._curve import FastAppendCurve
|
||||
from ._style import (
|
||||
_config_fonts_to_screen,
|
||||
hcolor,
|
||||
CHART_MARGINS,
|
||||
_xaxis_at,
|
||||
|
@ -67,7 +68,7 @@ from ..data import maybe_open_shm_array
|
|||
from .. import brokers
|
||||
from .. import data
|
||||
from ..log import get_logger
|
||||
from ._exec import run_qtractor
|
||||
from ._exec import run_qtractor, current_screen
|
||||
from ._interaction import ChartView
|
||||
from .order_mode import start_order_mode
|
||||
from .. import fsp
|
||||
|
@ -1686,7 +1687,7 @@ async def _async_main(
|
|||
chart_app = main_widget
|
||||
|
||||
# attempt to configure DPI aware font size
|
||||
screen = chart_app.window.current_screen()
|
||||
screen = current_screen()
|
||||
|
||||
# configure graphics update throttling based on display refresh rate
|
||||
global _clear_throttle_rate
|
||||
|
@ -1696,6 +1697,9 @@ async def _async_main(
|
|||
)
|
||||
log.info(f'Set graphics update rate to {_clear_throttle_rate} Hz')
|
||||
|
||||
# configure global DPI aware font size
|
||||
_config_fonts_to_screen()
|
||||
|
||||
# TODO: do styling / themeing setup
|
||||
# _style.style_ze_sheets(chart_app)
|
||||
|
||||
|
|
|
@ -21,7 +21,10 @@ 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
|
||||
import os
|
||||
import platform
|
||||
import signal
|
||||
import time
|
||||
import traceback
|
||||
|
||||
# Qt specific
|
||||
|
@ -29,7 +32,7 @@ import PyQt5 # noqa
|
|||
import pyqtgraph as pg
|
||||
from pyqtgraph import QtGui
|
||||
from PyQt5 import QtCore
|
||||
# from PyQt5.QtGui import QLabel, QStatusBar
|
||||
from PyQt5.QtGui import QLabel, QStatusBar
|
||||
from PyQt5.QtCore import (
|
||||
pyqtRemoveInputHook,
|
||||
Qt,
|
||||
|
@ -44,7 +47,6 @@ from outcome import Error
|
|||
from .._daemon import maybe_open_pikerd, _tractor_kwargs
|
||||
from ..log import get_logger
|
||||
from ._pg_overrides import _do_overrides
|
||||
from . import _style
|
||||
|
||||
log = get_logger(__name__)
|
||||
|
||||
|
@ -58,6 +60,34 @@ pg.enableExperimental = True
|
|||
_do_overrides()
|
||||
|
||||
|
||||
# singleton app per actor
|
||||
_qt_app: QtGui.QApplication = None
|
||||
_qt_win: QtGui.QMainWindow = None
|
||||
|
||||
|
||||
def current_screen() -> QtGui.QScreen:
|
||||
"""Get a frickin screen (if we can, gawd).
|
||||
|
||||
"""
|
||||
global _qt_win, _qt_app
|
||||
|
||||
for _ in range(3):
|
||||
screen = _qt_app.screenAt(_qt_win.pos())
|
||||
print('trying to access QScreen...')
|
||||
if screen is None:
|
||||
time.sleep(0.5)
|
||||
continue
|
||||
|
||||
break
|
||||
else:
|
||||
if screen is None:
|
||||
# try for the first one we can find
|
||||
screen = _qt_app.screens()[0]
|
||||
|
||||
assert screen, "Wow Qt is dumb as shit and has no screen..."
|
||||
return screen
|
||||
|
||||
|
||||
# XXX: pretty sure none of this shit works on linux as per:
|
||||
# https://bugreports.qt.io/browse/QTBUG-53022
|
||||
# it seems to work on windows.. no idea wtf is up.
|
||||
|
@ -72,12 +102,126 @@ if platform.system() == "Windows":
|
|||
QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
|
||||
|
||||
|
||||
class MultiStatus:
|
||||
|
||||
bar: QStatusBar
|
||||
statuses: list[str]
|
||||
|
||||
def __init__(self, bar, statuses) -> None:
|
||||
self.bar = bar
|
||||
self.statuses = statuses
|
||||
|
||||
def open_status(
|
||||
self,
|
||||
msg: str,
|
||||
) -> Callable[..., None]:
|
||||
'''Add a status to the status bar and return a close callback which
|
||||
when called will remove the status ``msg``.
|
||||
|
||||
'''
|
||||
self.statuses.append(msg)
|
||||
|
||||
def remove_msg() -> None:
|
||||
self.statuses.remove(msg)
|
||||
self.render()
|
||||
|
||||
self.render()
|
||||
return remove_msg
|
||||
|
||||
def render(self) -> None:
|
||||
if self.statuses:
|
||||
self.bar.showMessage(f'{" ".join(self.statuses)}')
|
||||
else:
|
||||
self.bar.clearMessage()
|
||||
|
||||
|
||||
class MainWindow(QtGui.QMainWindow):
|
||||
|
||||
size = (800, 500)
|
||||
title = 'piker chart (ur symbol is loading bby)'
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setMinimumSize(*self.size)
|
||||
self.setWindowTitle(self.title)
|
||||
|
||||
self._status_bar: QStatusBar = None
|
||||
self._status_label: QLabel = None
|
||||
|
||||
@property
|
||||
def mode_label(self) -> QtGui.QLabel:
|
||||
|
||||
# init mode label
|
||||
if not self._status_label:
|
||||
# TODO: i guess refactor stuff to avoid having to import here?
|
||||
from ._style import _font_small, hcolor
|
||||
self._status_label = label = QtGui.QLabel()
|
||||
label.setStyleSheet(
|
||||
f"QLabel {{ color : {hcolor('gunmetal')}; }}"
|
||||
)
|
||||
label.setTextFormat(3) # markdown
|
||||
label.setFont(_font_small.font)
|
||||
label.setMargin(2)
|
||||
label.setAlignment(
|
||||
QtCore.Qt.AlignVCenter
|
||||
| QtCore.Qt.AlignRight
|
||||
)
|
||||
self.statusBar().addPermanentWidget(label)
|
||||
label.show()
|
||||
|
||||
return self._status_label
|
||||
|
||||
def closeEvent(
|
||||
self,
|
||||
event: QtGui.QCloseEvent,
|
||||
) -> None:
|
||||
"""Cancel the root actor asap.
|
||||
|
||||
"""
|
||||
# raising KBI seems to get intercepted by by Qt so just use the system.
|
||||
os.kill(os.getpid(), signal.SIGINT)
|
||||
|
||||
@property
|
||||
def status_bar(self) -> QStatusBar:
|
||||
|
||||
# style and cached the status bar on first access
|
||||
if not self._status_bar:
|
||||
# TODO: i guess refactor stuff to avoid having to import here?
|
||||
from ._style import _font_small, hcolor
|
||||
sb = self.statusBar()
|
||||
sb.setStyleSheet((
|
||||
f"color : {hcolor('gunmetal')};"
|
||||
f"background : {hcolor('default_dark')};"
|
||||
f"font-size : {_font_small.px_size}px;"
|
||||
"padding : 0px;"
|
||||
# "min-height : 19px;"
|
||||
# "qproperty-alignment: AlignVCenter;"
|
||||
))
|
||||
self.setStatusBar(sb)
|
||||
self._status_bar = MultiStatus(sb, [])
|
||||
|
||||
return self._status_bar
|
||||
|
||||
def on_focus_change(
|
||||
self,
|
||||
old: QtGui.QWidget,
|
||||
new: QtGui.QWidget,
|
||||
) -> None:
|
||||
|
||||
log.debug(f'widget focus changed from {old} -> {new}')
|
||||
|
||||
if new is not None:
|
||||
# cursor left window?
|
||||
name = getattr(new, 'mode_name', '')
|
||||
self.mode_label.setText(name)
|
||||
|
||||
|
||||
def run_qtractor(
|
||||
func: Callable,
|
||||
args: Tuple,
|
||||
main_widget: QtGui.QWidget,
|
||||
tractor_kwargs: Dict[str, Any] = {},
|
||||
window_type: QtGui.QMainWindow = None,
|
||||
window_type: QtGui.QMainWindow = MainWindow,
|
||||
) -> None:
|
||||
# avoids annoying message when entering debugger from qt loop
|
||||
pyqtRemoveInputHook()
|
||||
|
@ -95,6 +239,10 @@ def run_qtractor(
|
|||
# XXX: lmfao, this is how you disable text edit cursor blinking..smh
|
||||
app.setCursorFlashTime(0)
|
||||
|
||||
# set global app singleton
|
||||
global _qt_app
|
||||
_qt_app = app
|
||||
|
||||
# This code is from Nathaniel, and I quote:
|
||||
# "This is substantially faster than using a signal... for some
|
||||
# reason Qt signal dispatch is really slow (and relies on events
|
||||
|
@ -138,20 +286,8 @@ def run_qtractor(
|
|||
app.setStyleSheet(stylesheet)
|
||||
|
||||
# make window and exec
|
||||
from . import _window
|
||||
|
||||
if window_type is None:
|
||||
window_type = _window.MainWindow
|
||||
|
||||
window = window_type()
|
||||
|
||||
# set global app's main window singleton
|
||||
_window._qt_win = window
|
||||
|
||||
# configure global DPI aware font sizes now that a screen
|
||||
# should be active from which we can read a DPI.
|
||||
_style._config_fonts_to_screen()
|
||||
|
||||
# hook into app focus change events
|
||||
app.focusChanged.connect(window.on_focus_change)
|
||||
|
||||
|
@ -180,6 +316,11 @@ def run_qtractor(
|
|||
window.main_widget = main_widget
|
||||
window.setCentralWidget(instance)
|
||||
|
||||
# store global ref
|
||||
# set global app singleton
|
||||
global _qt_win
|
||||
_qt_win = window
|
||||
|
||||
# actually render to screen
|
||||
window.show()
|
||||
app.exec_()
|
||||
|
|
|
@ -25,6 +25,7 @@ from PyQt5 import QtCore, QtGui
|
|||
from qdarkstyle import DarkPalette
|
||||
|
||||
from ..log import get_logger
|
||||
from ._exec import current_screen
|
||||
|
||||
log = get_logger(__name__)
|
||||
|
||||
|
@ -66,15 +67,13 @@ class DpiAwareFont:
|
|||
|
||||
@property
|
||||
def screen(self) -> QtGui.QScreen:
|
||||
from ._window import main_window
|
||||
|
||||
if self._screen is not None:
|
||||
try:
|
||||
self._screen.refreshRate()
|
||||
except RuntimeError:
|
||||
self._screen = main_window().current_screen()
|
||||
self._screen = current_screen()
|
||||
else:
|
||||
self._screen = main_window().current_screen()
|
||||
self._screen = current_screen()
|
||||
|
||||
return self._screen
|
||||
|
||||
|
@ -150,8 +149,6 @@ _font_small = DpiAwareFont(font_size='small')
|
|||
|
||||
|
||||
def _config_fonts_to_screen() -> None:
|
||||
'configure global DPI aware font sizes'
|
||||
|
||||
global _font, _font_small
|
||||
_font.configure_to_dpi()
|
||||
_font_small.configure_to_dpi()
|
||||
|
|
|
@ -1,180 +0,0 @@
|
|||
# piker: trading gear for hackers
|
||||
# Copyright (C) Tyler Goodlet (in stewardship for piker0)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Qt main window singletons and stuff.
|
||||
|
||||
"""
|
||||
import os
|
||||
import signal
|
||||
import time
|
||||
from typing import Callable
|
||||
|
||||
from pyqtgraph import QtGui
|
||||
from PyQt5 import QtCore
|
||||
from PyQt5.QtGui import QLabel, QStatusBar
|
||||
|
||||
from ..log import get_logger
|
||||
from ._style import _font_small, hcolor
|
||||
|
||||
|
||||
log = get_logger(__name__)
|
||||
|
||||
|
||||
class MultiStatus:
|
||||
|
||||
bar: QStatusBar
|
||||
statuses: list[str]
|
||||
|
||||
def __init__(self, bar, statuses) -> None:
|
||||
self.bar = bar
|
||||
self.statuses = statuses
|
||||
|
||||
def open_status(
|
||||
self,
|
||||
msg: str,
|
||||
) -> Callable[..., None]:
|
||||
'''Add a status to the status bar and return a close callback which
|
||||
when called will remove the status ``msg``.
|
||||
|
||||
'''
|
||||
self.statuses.append(msg)
|
||||
|
||||
def remove_msg() -> None:
|
||||
self.statuses.remove(msg)
|
||||
self.render()
|
||||
|
||||
self.render()
|
||||
return remove_msg
|
||||
|
||||
def render(self) -> None:
|
||||
if self.statuses:
|
||||
self.bar.showMessage(f'{" ".join(self.statuses)}')
|
||||
else:
|
||||
self.bar.clearMessage()
|
||||
|
||||
|
||||
class MainWindow(QtGui.QMainWindow):
|
||||
|
||||
size = (800, 500)
|
||||
title = 'piker chart (ur symbol is loading bby)'
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setMinimumSize(*self.size)
|
||||
self.setWindowTitle(self.title)
|
||||
|
||||
self._status_bar: QStatusBar = None
|
||||
self._status_label: QLabel = None
|
||||
|
||||
@property
|
||||
def mode_label(self) -> QtGui.QLabel:
|
||||
|
||||
# init mode label
|
||||
if not self._status_label:
|
||||
|
||||
self._status_label = label = QtGui.QLabel()
|
||||
label.setStyleSheet(
|
||||
f"QLabel {{ color : {hcolor('gunmetal')}; }}"
|
||||
)
|
||||
label.setTextFormat(3) # markdown
|
||||
label.setFont(_font_small.font)
|
||||
label.setMargin(2)
|
||||
label.setAlignment(
|
||||
QtCore.Qt.AlignVCenter
|
||||
| QtCore.Qt.AlignRight
|
||||
)
|
||||
self.statusBar().addPermanentWidget(label)
|
||||
label.show()
|
||||
|
||||
return self._status_label
|
||||
|
||||
def closeEvent(
|
||||
self,
|
||||
event: QtGui.QCloseEvent,
|
||||
) -> None:
|
||||
"""Cancel the root actor asap.
|
||||
|
||||
"""
|
||||
# raising KBI seems to get intercepted by by Qt so just use the system.
|
||||
os.kill(os.getpid(), signal.SIGINT)
|
||||
|
||||
@property
|
||||
def status_bar(self) -> QStatusBar:
|
||||
|
||||
# style and cached the status bar on first access
|
||||
if not self._status_bar:
|
||||
|
||||
sb = self.statusBar()
|
||||
sb.setStyleSheet((
|
||||
f"color : {hcolor('gunmetal')};"
|
||||
f"background : {hcolor('default_dark')};"
|
||||
f"font-size : {_font_small.px_size}px;"
|
||||
"padding : 0px;"
|
||||
# "min-height : 19px;"
|
||||
# "qproperty-alignment: AlignVCenter;"
|
||||
))
|
||||
self.setStatusBar(sb)
|
||||
self._status_bar = MultiStatus(sb, [])
|
||||
|
||||
return self._status_bar
|
||||
|
||||
def on_focus_change(
|
||||
self,
|
||||
old: QtGui.QWidget,
|
||||
new: QtGui.QWidget,
|
||||
) -> None:
|
||||
|
||||
log.debug(f'widget focus changed from {old} -> {new}')
|
||||
|
||||
if new is not None:
|
||||
# cursor left window?
|
||||
name = getattr(new, 'mode_name', '')
|
||||
self.mode_label.setText(name)
|
||||
|
||||
def current_screen(self) -> QtGui.QScreen:
|
||||
"""Get a frickin screen (if we can, gawd).
|
||||
|
||||
"""
|
||||
app = QtGui.QApplication.instance()
|
||||
|
||||
for _ in range(3):
|
||||
screen = app.screenAt(self.pos())
|
||||
print('trying to access QScreen...')
|
||||
if screen is None:
|
||||
time.sleep(0.5)
|
||||
continue
|
||||
|
||||
break
|
||||
else:
|
||||
if screen is None:
|
||||
# try for the first one we can find
|
||||
screen = app.screens()[0]
|
||||
|
||||
assert screen, "Wow Qt is dumb as shit and has no screen..."
|
||||
return screen
|
||||
|
||||
|
||||
# singleton app per actor
|
||||
_qt_win: QtGui.QMainWindow = None
|
||||
|
||||
|
||||
def main_window() -> MainWindow:
|
||||
'Return the actor-global Qt window.'
|
||||
|
||||
global _qt_win
|
||||
assert _qt_win
|
||||
return _qt_win
|
Loading…
Reference in New Issue