piker/piker/ui/_exec.py

216 lines
5.7 KiB
Python
Raw Normal View History

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/>.
"""
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
import os
import signal
2021-01-22 21:46:39 +00:00
import time
import traceback
# Qt specific
import PyQt5 # noqa
import pyqtgraph as pg
from pyqtgraph import QtGui
from PyQt5 import QtCore
from PyQt5.QtCore import (
2021-01-22 21:46:39 +00:00
pyqtRemoveInputHook,
Qt,
QCoreApplication,
)
import qdarkstyle
import trio
import tractor
from outcome import Error
2021-01-22 21:46:39 +00:00
from ..log import get_logger
log = get_logger(__name__)
# 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
# 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)
class MainWindow(QtGui.QMainWindow):
size = (800, 500)
title = 'piker chart (ur symbol is loading bby)'
def __init__(self, parent=None):
super().__init__(parent)
2020-11-03 17:22:57 +00:00
self.setMinimumSize(*self.size)
self.setWindowTitle(self.title)
def closeEvent(
self,
2021-01-22 21:46:39 +00:00
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)
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,
tractor_kwargs: Dict[str, Any] = {},
2020-07-15 13:55:09 +00:00
window_type: QtGui.QMainWindow = MainWindow,
) -> 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
# 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
# underneath anyway, so this is strictly less work)."
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
if isinstance(outcome, Error):
exc = outcome.error
if isinstance(outcome.error, KeyboardInterrupt):
2020-12-14 17:36:38 +00:00
# make it kinda look like ``trio``
print("Terminated!")
else:
traceback.print_exception(type(exc), exc, exc.__traceback__)
2020-10-27 19:15:31 +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()
instance = main_widget()
2020-07-15 13:55:09 +00:00
instance.window = window
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
trio.lowlevel.start_guest_run(
main,
run_sync_soon_threadsafe=run_sync_soon_threadsafe,
done_callback=done_callback,
# restrict_keyboard_interrupt_to_checkpoints=True,
)
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
# actually render to screen
window.show()
app.exec_()