2020-11-06 17:23:14 +00:00
|
|
|
# piker: trading gear for hackers
|
|
|
|
# Copyright (C) 2018-present Tyler Goodlet (in stewardship of 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/>.
|
|
|
|
|
2020-06-10 16:48:35 +00:00
|
|
|
"""
|
|
|
|
Trio - Qt integration
|
|
|
|
|
|
|
|
Run ``trio`` in guest mode on top of the Qt event loop.
|
|
|
|
All global Qt runtime settings are mostly defined here.
|
|
|
|
"""
|
2021-01-22 21:46:39 +00:00
|
|
|
from typing import Tuple, Callable, Dict, Any
|
2020-12-13 18:11:23 +00:00
|
|
|
import os
|
|
|
|
import signal
|
2021-01-22 21:46:39 +00:00
|
|
|
import time
|
2020-06-10 16:48:35 +00:00
|
|
|
import traceback
|
|
|
|
|
2020-09-29 15:11:42 +00:00
|
|
|
# Qt specific
|
2020-06-10 16:48:35 +00:00
|
|
|
import PyQt5 # noqa
|
2020-11-16 15:18:15 +00:00
|
|
|
import pyqtgraph as pg
|
2020-06-10 16:48:35 +00:00
|
|
|
from pyqtgraph import QtGui
|
|
|
|
from PyQt5 import QtCore
|
2020-09-29 15:11:42 +00:00
|
|
|
from PyQt5.QtCore import (
|
2021-01-22 21:46:39 +00:00
|
|
|
pyqtRemoveInputHook,
|
|
|
|
Qt,
|
|
|
|
QCoreApplication,
|
2020-09-29 15:11:42 +00:00
|
|
|
)
|
2020-06-10 16:48:35 +00:00
|
|
|
import qdarkstyle
|
|
|
|
import trio
|
2020-07-08 19:40:35 +00:00
|
|
|
import tractor
|
2020-06-10 16:48:35 +00:00
|
|
|
from outcome import Error
|
|
|
|
|
2021-01-22 21:46:39 +00:00
|
|
|
from ..log import get_logger
|
|
|
|
|
|
|
|
log = get_logger(__name__)
|
2020-06-10 16:48:35 +00:00
|
|
|
|
2020-11-16 15:18:15 +00:00
|
|
|
# pyqtgraph global config
|
|
|
|
# might as well enable this for now?
|
|
|
|
pg.useOpenGL = True
|
|
|
|
pg.enableExperimental = True
|
|
|
|
|
|
|
|
|
2020-10-27 19:15:31 +00:00
|
|
|
# singleton app per actor
|
|
|
|
_qt_app: QtGui.QApplication = None
|
|
|
|
_qt_win: QtGui.QMainWindow = None
|
|
|
|
|
|
|
|
|
2021-01-22 21:46:39 +00:00
|
|
|
def current_screen(timeout: float = 6) -> QtGui.QScreen:
|
|
|
|
print('yo screen zonnnee')
|
2020-10-27 19:15:31 +00:00
|
|
|
|
|
|
|
global _qt_win, _qt_app
|
2021-01-22 21:46:39 +00:00
|
|
|
screen = _qt_app.screenAt(_qt_win.centralWidget().geometry().center())
|
|
|
|
|
|
|
|
start = time.time()
|
|
|
|
|
|
|
|
# breakpoint()
|
|
|
|
# wait for 6 seconds to grab screen
|
|
|
|
while screen is None and (
|
|
|
|
(time.time() - start) < timeout
|
|
|
|
):
|
|
|
|
screen = _qt_app.screenAt(_qt_win.centralWidget().geometry().center())
|
|
|
|
time.sleep(0.1)
|
|
|
|
log.info("Couldn't acquire screen trying again...")
|
|
|
|
|
|
|
|
if screen is None:
|
|
|
|
raise RuntimeError("Failed to acquire screen?")
|
|
|
|
|
|
|
|
return screen
|
2020-10-27 19:15:31 +00:00
|
|
|
|
|
|
|
|
2020-09-29 15:11:42 +00:00
|
|
|
# Proper high DPI scaling is available in Qt >= 5.6.0. This attibute
|
|
|
|
# must be set before creating the application
|
|
|
|
if hasattr(Qt, 'AA_EnableHighDpiScaling'):
|
|
|
|
QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
|
|
|
|
|
2020-09-29 20:24:59 +00:00
|
|
|
if hasattr(Qt, 'AA_UseHighDpiPixmaps'):
|
|
|
|
QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
|
2020-09-29 15:11:42 +00:00
|
|
|
|
|
|
|
|
2020-06-10 16:48:35 +00:00
|
|
|
class MainWindow(QtGui.QMainWindow):
|
|
|
|
|
|
|
|
size = (800, 500)
|
2020-11-06 20:35:10 +00:00
|
|
|
title = 'piker chart (ur symbol is loading bby)'
|
2020-06-10 16:48:35 +00:00
|
|
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
super().__init__(parent)
|
2020-11-03 17:22:57 +00:00
|
|
|
self.setMinimumSize(*self.size)
|
2020-06-10 16:48:35 +00:00
|
|
|
self.setWindowTitle(self.title)
|
|
|
|
|
2020-12-13 18:11:23 +00:00
|
|
|
def closeEvent(
|
|
|
|
self,
|
2021-01-22 21:46:39 +00:00
|
|
|
event: QtGui.QCloseEvent,
|
2020-12-13 18:11:23 +00:00
|
|
|
) -> None:
|
2020-11-16 15:18:15 +00:00
|
|
|
"""Cancel the root actor asap.
|
|
|
|
|
|
|
|
"""
|
2020-12-26 22:51:01 +00:00
|
|
|
# raising KBI seems to get intercepted by by Qt so just use the system.
|
2020-12-13 18:11:23 +00:00
|
|
|
os.kill(os.getpid(), signal.SIGINT)
|
2020-11-16 15:18:15 +00:00
|
|
|
|
2020-06-10 16:48:35 +00:00
|
|
|
|
2020-07-15 13:55:09 +00:00
|
|
|
def run_qtractor(
|
2020-08-03 00:10:06 +00:00
|
|
|
func: Callable,
|
|
|
|
args: Tuple,
|
2020-07-15 13:55:09 +00:00
|
|
|
main_widget: QtGui.QWidget,
|
2020-08-19 11:41:18 +00:00
|
|
|
tractor_kwargs: Dict[str, Any] = {},
|
2020-07-15 13:55:09 +00:00
|
|
|
window_type: QtGui.QMainWindow = MainWindow,
|
2020-06-10 16:48:35 +00:00
|
|
|
) -> None:
|
|
|
|
# avoids annoying message when entering debugger from qt loop
|
|
|
|
pyqtRemoveInputHook()
|
|
|
|
|
|
|
|
app = QtGui.QApplication.instance()
|
|
|
|
if app is None:
|
|
|
|
app = PyQt5.QtWidgets.QApplication([])
|
|
|
|
|
2020-07-15 13:55:09 +00:00
|
|
|
# TODO: we might not need this if it's desired
|
|
|
|
# to cancel the tractor machinery on Qt loop
|
|
|
|
# close, however the details of doing that correctly
|
|
|
|
# currently seem tricky..
|
|
|
|
app.setQuitOnLastWindowClosed(False)
|
|
|
|
|
2020-10-27 19:15:31 +00:00
|
|
|
# set global app singleton
|
|
|
|
global _qt_app
|
|
|
|
_qt_app = app
|
|
|
|
|
2020-06-13 23:25:44 +00:00
|
|
|
# This code is from Nathaniel, and I quote:
|
|
|
|
# "This is substantially faster than using a signal... for some
|
2020-06-10 16:48:35 +00:00
|
|
|
# reason Qt signal dispatch is really slow (and relies on events
|
2020-06-13 23:25:44 +00:00
|
|
|
# underneath anyway, so this is strictly less work)."
|
2020-06-10 16:48:35 +00:00
|
|
|
REENTER_EVENT = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
|
|
|
|
|
|
|
|
class ReenterEvent(QtCore.QEvent):
|
|
|
|
pass
|
|
|
|
|
|
|
|
class Reenter(QtCore.QObject):
|
|
|
|
def event(self, event):
|
|
|
|
event.fn()
|
|
|
|
return False
|
|
|
|
|
|
|
|
reenter = Reenter()
|
|
|
|
|
|
|
|
def run_sync_soon_threadsafe(fn):
|
|
|
|
event = ReenterEvent(REENTER_EVENT)
|
|
|
|
event.fn = fn
|
|
|
|
app.postEvent(reenter, event)
|
|
|
|
|
|
|
|
def done_callback(outcome):
|
2020-10-27 19:15:31 +00:00
|
|
|
|
2020-06-10 16:48:35 +00:00
|
|
|
if isinstance(outcome, Error):
|
|
|
|
exc = outcome.error
|
2020-12-13 18:11:23 +00:00
|
|
|
|
|
|
|
if isinstance(outcome.error, KeyboardInterrupt):
|
2020-12-14 17:36:38 +00:00
|
|
|
# make it kinda look like ``trio``
|
|
|
|
print("Terminated!")
|
2020-12-13 18:11:23 +00:00
|
|
|
|
|
|
|
else:
|
|
|
|
traceback.print_exception(type(exc), exc, exc.__traceback__)
|
2020-10-27 19:15:31 +00:00
|
|
|
|
2020-06-10 16:48:35 +00:00
|
|
|
app.quit()
|
|
|
|
|
|
|
|
# load dark theme
|
|
|
|
app.setStyleSheet(qdarkstyle.load_stylesheet(qt_api='pyqt5'))
|
|
|
|
|
|
|
|
# make window and exec
|
2020-07-15 13:55:09 +00:00
|
|
|
window = window_type()
|
2020-06-10 16:48:35 +00:00
|
|
|
instance = main_widget()
|
2020-07-15 13:55:09 +00:00
|
|
|
instance.window = window
|
2020-06-10 16:48:35 +00:00
|
|
|
|
|
|
|
widgets = {
|
|
|
|
'window': window,
|
|
|
|
'main': instance,
|
|
|
|
}
|
|
|
|
|
2020-12-28 18:07:17 +00:00
|
|
|
# define tractor entrypoint
|
|
|
|
async def main():
|
|
|
|
|
|
|
|
async with tractor.open_root_actor(
|
|
|
|
arbiter_addr=(
|
|
|
|
tractor._root._default_arbiter_host,
|
|
|
|
tractor._root._default_arbiter_port,
|
|
|
|
),
|
|
|
|
name='qtractor',
|
|
|
|
**tractor_kwargs,
|
2021-01-22 21:46:39 +00:00
|
|
|
):
|
2020-12-28 18:07:17 +00:00
|
|
|
await func(*(args + (widgets,)))
|
|
|
|
|
|
|
|
# guest mode entry
|
2020-06-10 16:48:35 +00:00
|
|
|
trio.lowlevel.start_guest_run(
|
2020-08-19 11:41:18 +00:00
|
|
|
main,
|
2020-06-10 16:48:35 +00:00
|
|
|
run_sync_soon_threadsafe=run_sync_soon_threadsafe,
|
|
|
|
done_callback=done_callback,
|
2020-12-13 18:11:23 +00:00
|
|
|
# restrict_keyboard_interrupt_to_checkpoints=True,
|
2020-06-10 16:48:35 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
window.main_widget = main_widget
|
|
|
|
window.setCentralWidget(instance)
|
2020-10-27 19:15:31 +00:00
|
|
|
|
|
|
|
# store global ref
|
|
|
|
# set global app singleton
|
|
|
|
global _qt_win
|
|
|
|
_qt_win = window
|
|
|
|
|
2020-06-14 19:10:08 +00:00
|
|
|
# actually render to screen
|
2020-06-10 16:48:35 +00:00
|
|
|
window.show()
|
|
|
|
app.exec_()
|