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._ohlc import BarItems
|
||||||
from ._graphics._curve import FastAppendCurve
|
from ._graphics._curve import FastAppendCurve
|
||||||
from ._style import (
|
from ._style import (
|
||||||
|
_config_fonts_to_screen,
|
||||||
hcolor,
|
hcolor,
|
||||||
CHART_MARGINS,
|
CHART_MARGINS,
|
||||||
_xaxis_at,
|
_xaxis_at,
|
||||||
|
@ -67,7 +68,7 @@ from ..data import maybe_open_shm_array
|
||||||
from .. import brokers
|
from .. import brokers
|
||||||
from .. import data
|
from .. import data
|
||||||
from ..log import get_logger
|
from ..log import get_logger
|
||||||
from ._exec import run_qtractor
|
from ._exec import run_qtractor, current_screen
|
||||||
from ._interaction import ChartView
|
from ._interaction import ChartView
|
||||||
from .order_mode import start_order_mode
|
from .order_mode import start_order_mode
|
||||||
from .. import fsp
|
from .. import fsp
|
||||||
|
@ -1686,7 +1687,7 @@ async def _async_main(
|
||||||
chart_app = main_widget
|
chart_app = main_widget
|
||||||
|
|
||||||
# attempt to configure DPI aware font size
|
# 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
|
# configure graphics update throttling based on display refresh rate
|
||||||
global _clear_throttle_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')
|
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
|
# TODO: do styling / themeing setup
|
||||||
# _style.style_ze_sheets(chart_app)
|
# _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.
|
All global Qt runtime settings are mostly defined here.
|
||||||
"""
|
"""
|
||||||
from typing import Tuple, Callable, Dict, Any
|
from typing import Tuple, Callable, Dict, Any
|
||||||
|
import os
|
||||||
import platform
|
import platform
|
||||||
|
import signal
|
||||||
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
# Qt specific
|
# Qt specific
|
||||||
|
@ -29,7 +32,7 @@ import PyQt5 # noqa
|
||||||
import pyqtgraph as pg
|
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
|
||||||
from PyQt5.QtCore import (
|
from PyQt5.QtCore import (
|
||||||
pyqtRemoveInputHook,
|
pyqtRemoveInputHook,
|
||||||
Qt,
|
Qt,
|
||||||
|
@ -44,7 +47,6 @@ from outcome import Error
|
||||||
from .._daemon import maybe_open_pikerd, _tractor_kwargs
|
from .._daemon import maybe_open_pikerd, _tractor_kwargs
|
||||||
from ..log import get_logger
|
from ..log import get_logger
|
||||||
from ._pg_overrides import _do_overrides
|
from ._pg_overrides import _do_overrides
|
||||||
from . import _style
|
|
||||||
|
|
||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -58,6 +60,34 @@ pg.enableExperimental = True
|
||||||
_do_overrides()
|
_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:
|
# XXX: pretty sure none of this shit works on linux as per:
|
||||||
# https://bugreports.qt.io/browse/QTBUG-53022
|
# https://bugreports.qt.io/browse/QTBUG-53022
|
||||||
# it seems to work on windows.. no idea wtf is up.
|
# 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)
|
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(
|
def run_qtractor(
|
||||||
func: Callable,
|
func: Callable,
|
||||||
args: Tuple,
|
args: Tuple,
|
||||||
main_widget: QtGui.QWidget,
|
main_widget: QtGui.QWidget,
|
||||||
tractor_kwargs: Dict[str, Any] = {},
|
tractor_kwargs: Dict[str, Any] = {},
|
||||||
window_type: QtGui.QMainWindow = None,
|
window_type: QtGui.QMainWindow = MainWindow,
|
||||||
) -> None:
|
) -> None:
|
||||||
# avoids annoying message when entering debugger from qt loop
|
# avoids annoying message when entering debugger from qt loop
|
||||||
pyqtRemoveInputHook()
|
pyqtRemoveInputHook()
|
||||||
|
@ -95,6 +239,10 @@ def run_qtractor(
|
||||||
# XXX: lmfao, this is how you disable text edit cursor blinking..smh
|
# XXX: lmfao, this is how you disable text edit cursor blinking..smh
|
||||||
app.setCursorFlashTime(0)
|
app.setCursorFlashTime(0)
|
||||||
|
|
||||||
|
# set global app singleton
|
||||||
|
global _qt_app
|
||||||
|
_qt_app = app
|
||||||
|
|
||||||
# This code is from Nathaniel, and I quote:
|
# This code is from Nathaniel, and I quote:
|
||||||
# "This is substantially faster than using a signal... for some
|
# "This is substantially faster than using a signal... for some
|
||||||
# reason Qt signal dispatch is really slow (and relies on events
|
# reason Qt signal dispatch is really slow (and relies on events
|
||||||
|
@ -138,20 +286,8 @@ def run_qtractor(
|
||||||
app.setStyleSheet(stylesheet)
|
app.setStyleSheet(stylesheet)
|
||||||
|
|
||||||
# make window and exec
|
# make window and exec
|
||||||
from . import _window
|
|
||||||
|
|
||||||
if window_type is None:
|
|
||||||
window_type = _window.MainWindow
|
|
||||||
|
|
||||||
window = window_type()
|
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
|
# hook into app focus change events
|
||||||
app.focusChanged.connect(window.on_focus_change)
|
app.focusChanged.connect(window.on_focus_change)
|
||||||
|
|
||||||
|
@ -180,6 +316,11 @@ def run_qtractor(
|
||||||
window.main_widget = main_widget
|
window.main_widget = main_widget
|
||||||
window.setCentralWidget(instance)
|
window.setCentralWidget(instance)
|
||||||
|
|
||||||
|
# store global ref
|
||||||
|
# set global app singleton
|
||||||
|
global _qt_win
|
||||||
|
_qt_win = window
|
||||||
|
|
||||||
# actually render to screen
|
# actually render to screen
|
||||||
window.show()
|
window.show()
|
||||||
app.exec_()
|
app.exec_()
|
||||||
|
|
|
@ -25,6 +25,7 @@ from PyQt5 import QtCore, QtGui
|
||||||
from qdarkstyle import DarkPalette
|
from qdarkstyle import DarkPalette
|
||||||
|
|
||||||
from ..log import get_logger
|
from ..log import get_logger
|
||||||
|
from ._exec import current_screen
|
||||||
|
|
||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -66,15 +67,13 @@ class DpiAwareFont:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def screen(self) -> QtGui.QScreen:
|
def screen(self) -> QtGui.QScreen:
|
||||||
from ._window import main_window
|
|
||||||
|
|
||||||
if self._screen is not None:
|
if self._screen is not None:
|
||||||
try:
|
try:
|
||||||
self._screen.refreshRate()
|
self._screen.refreshRate()
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
self._screen = main_window().current_screen()
|
self._screen = current_screen()
|
||||||
else:
|
else:
|
||||||
self._screen = main_window().current_screen()
|
self._screen = current_screen()
|
||||||
|
|
||||||
return self._screen
|
return self._screen
|
||||||
|
|
||||||
|
@ -150,8 +149,6 @@ _font_small = DpiAwareFont(font_size='small')
|
||||||
|
|
||||||
|
|
||||||
def _config_fonts_to_screen() -> None:
|
def _config_fonts_to_screen() -> None:
|
||||||
'configure global DPI aware font sizes'
|
|
||||||
|
|
||||||
global _font, _font_small
|
global _font, _font_small
|
||||||
_font.configure_to_dpi()
|
_font.configure_to_dpi()
|
||||||
_font_small.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