diff --git a/piker/ui/__init__.py b/piker/ui/__init__.py index 88771b2d..134e0da0 100644 --- a/piker/ui/__init__.py +++ b/piker/ui/__init__.py @@ -14,9 +14,8 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -""" -Stuff for your eyes, aka super hawt Qt UI components. +''' +UI components built using `Qt` with major versions swapped in via +the import indirection in the `.qt` sub-mod. -Currently we only support PyQt5 due to this issue in Pyside2: -https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-1313 -""" +''' diff --git a/piker/ui/_anchors.py b/piker/ui/_anchors.py index 5d8217c8..09bcf71d 100644 --- a/piker/ui/_anchors.py +++ b/piker/ui/_anchors.py @@ -21,8 +21,10 @@ Anchor funtions for UI placement of annotions. from __future__ import annotations from typing import Callable, TYPE_CHECKING -from PyQt5.QtCore import QPointF -from PyQt5.QtWidgets import QGraphicsPathItem +from piker.ui.qt import ( + QPointF, + QGraphicsPathItem, +) if TYPE_CHECKING: from ._chart import ChartPlotWidget diff --git a/piker/ui/_annotate.py b/piker/ui/_annotate.py index f3eeeb07..0dca9dcc 100644 --- a/piker/ui/_annotate.py +++ b/piker/ui/_annotate.py @@ -20,12 +20,22 @@ Annotations for ur faces. """ from typing import Callable -from PyQt5 import QtCore, QtGui, QtWidgets -from PyQt5.QtCore import QPointF, QRectF -from PyQt5.QtWidgets import QGraphicsPathItem -from pyqtgraph import Point, functions as fn, Color +from pyqtgraph import ( + Point, + functions as fn, + Color, +) import numpy as np +from piker.ui.qt import ( + QtCore, + QtGui, + QtWidgets, + QPointF, + QRectF, + QGraphicsPathItem, +) + def mk_marker_path( diff --git a/piker/ui/_app.py b/piker/ui/_app.py index 199ba656..5733e372 100644 --- a/piker/ui/_app.py +++ b/piker/ui/_app.py @@ -21,9 +21,11 @@ Main app startup and run. from functools import partial from types import ModuleType -from PyQt5.QtCore import QEvent import trio +from piker.ui.qt import ( + QEvent, +) from ..service import maybe_spawn_brokerd from . import _event from ._exec import run_qtractor diff --git a/piker/ui/_axes.py b/piker/ui/_axes.py index 461842db..5eab5afe 100644 --- a/piker/ui/_axes.py +++ b/piker/ui/_axes.py @@ -25,9 +25,16 @@ from math import floor import polars as pl import pyqtgraph as pg -from PyQt5 import QtCore, QtGui, QtWidgets -from PyQt5.QtCore import QPointF +from piker.ui.qt import ( + QtCore, + QtGui, + QtWidgets, + QPointF, + txt_flag, + align_flag, + px_cache_mode, +) from . import _pg_overrides as pgo from ..accounting._mktinfo import float_digits from ._label import Label @@ -414,11 +421,15 @@ class AxisLabel(pg.GraphicsObject): super().__init__() self.setParentItem(parent) - self.setFlag(self.ItemIgnoresTransformations) + self.setFlag( + self.GraphicsItemFlag.ItemIgnoresTransformations + ) self.setZValue(100) # XXX: pretty sure this is faster - self.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache) + self.setCacheMode( + px_cache_mode.DeviceCoordinateCache + ) self._parent = parent @@ -555,21 +566,14 @@ class AxisLabel(pg.GraphicsObject): return (self.rect.width(), self.rect.height()) -# _common_text_flags = ( -# QtCore.Qt.TextDontClip | -# QtCore.Qt.AlignCenter | -# QtCore.Qt.AlignTop | -# QtCore.Qt.AlignHCenter | -# QtCore.Qt.AlignVCenter -# ) class XAxisLabel(AxisLabel): _x_margin = 8 text_flags = ( - QtCore.Qt.TextDontClip - | QtCore.Qt.AlignCenter + align_flag.AlignCenter + | txt_flag.TextDontClip ) def size_hint(self) -> tuple[float, float]: @@ -626,10 +630,10 @@ class YAxisLabel(AxisLabel): _y_margin: int = 4 text_flags = ( - QtCore.Qt.AlignLeft - # QtCore.Qt.AlignHCenter - | QtCore.Qt.AlignVCenter - | QtCore.Qt.TextDontClip + align_flag.AlignLeft + | align_flag.AlignVCenter + # | align_flag.AlignHCenter + | txt_flag.TextDontClip ) def __init__( diff --git a/piker/ui/_chart.py b/piker/ui/_chart.py index e00ad70b..afcd7dd0 100644 --- a/piker/ui/_chart.py +++ b/piker/ui/_chart.py @@ -28,22 +28,20 @@ from typing import ( TYPE_CHECKING, ) -from PyQt5 import QtCore, QtWidgets -from PyQt5.QtCore import ( +import pyqtgraph as pg +import trio + +from piker.ui.qt import ( + QtCore, + QtWidgets, Qt, QLineF, - # QPointF, -) -from PyQt5.QtWidgets import ( QFrame, QWidget, QHBoxLayout, QVBoxLayout, QSplitter, ) -import pyqtgraph as pg -import trio - from ._axes import ( DynamicDateAxis, PriceAxis, @@ -570,8 +568,8 @@ class LinkedSplits(QWidget): # style? self.chart.setFrameStyle( - QFrame.StyledPanel | - QFrame.Plain + QFrame.Shape.StyledPanel | + QFrame.Shadow.Plain ) return self.chart @@ -689,8 +687,8 @@ class LinkedSplits(QWidget): cpw.plotItem.vb.linked = self cpw.setFrameStyle( - QtWidgets.QFrame.StyledPanel - # | QtWidgets.QFrame.Plain + QFrame.Shape.StyledPanel + # | QFrame.Shadow.Plain ) # don't show the little "autoscale" A label. diff --git a/piker/ui/_cursor.py b/piker/ui/_cursor.py index c14387e0..7675b2e0 100644 --- a/piker/ui/_cursor.py +++ b/piker/ui/_cursor.py @@ -28,9 +28,14 @@ from typing import ( import inspect import numpy as np import pyqtgraph as pg -from PyQt5 import QtCore, QtWidgets -from PyQt5.QtCore import QPointF, QRectF +from piker.ui.qt import ( + QPointF, + QRectF, + QtCore, + QtWidgets, + px_cache_mode, +) from ._style import ( _xaxis_at, hcolor, @@ -104,7 +109,9 @@ class LineDot(pg.CurvePoint): dot.setParentItem(self) # keep a static size - self.setFlag(self.ItemIgnoresTransformations) + self.setFlag( + self.GraphicsItemFlag.ItemIgnoresTransformations + ) def event( self, @@ -424,10 +431,10 @@ class Cursor(pg.GraphicsObject): # vertical and horizonal lines and a y-axis label vl = plot.addLine(x=0, pen=self.lines_pen, movable=False) - vl.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache) + vl.setCacheMode(px_cache_mode.DeviceCoordinateCache) hl = plot.addLine(y=0, pen=self.lines_pen, movable=False) - hl.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache) + hl.setCacheMode(px_cache_mode.DeviceCoordinateCache) hl.hide() yl = YAxisLabel( @@ -511,7 +518,10 @@ class Cursor(pg.GraphicsObject): plot=chart ) chart.addItem(cursor) - self.graphics[chart].setdefault('cursors', []).append(cursor) + self.graphics[chart].setdefault( + 'cursors', + [], + ).append(cursor) return cursor def mouseAction( diff --git a/piker/ui/_curve.py b/piker/ui/_curve.py index ff01c89d..dc368344 100644 --- a/piker/ui/_curve.py +++ b/piker/ui/_curve.py @@ -19,20 +19,21 @@ Fast, smooth, sexy curves. """ from contextlib import contextmanager as cm +from enum import EnumType from typing import Callable import numpy as np import pyqtgraph as pg -from PyQt5 import QtWidgets -from PyQt5.QtWidgets import QGraphicsItem -from PyQt5.QtCore import ( + +from piker.ui.qt import ( + QtWidgets, + QGraphicsItem, Qt, QLineF, QRectF, -) -from PyQt5.QtGui import ( QPainter, QPainterPath, + px_cache_mode, ) from ._style import hcolor from ..log import get_logger @@ -42,15 +43,16 @@ from ..toolz.profile import ( ms_slower_then, ) - log = get_logger(__name__) +pen_style: EnumType = Qt.PenStyle + _line_styles: dict[str, int] = { - 'solid': Qt.PenStyle.SolidLine, - 'dash': Qt.PenStyle.DashLine, - 'dot': Qt.PenStyle.DotLine, - 'dashdot': Qt.PenStyle.DashDotLine, + 'solid': pen_style.SolidLine, + 'dash': pen_style.DashLine, + 'dot': pen_style.DotLine, + 'dashdot': pen_style.DashDotLine, } @@ -69,12 +71,12 @@ class FlowGraphic(pg.GraphicsObject): # XXX-NOTE-XXX: graphics caching B) # see explanation for different caching modes: # https://stackoverflow.com/a/39410081 - cache_mode: int = QGraphicsItem.DeviceCoordinateCache + cache_mode: int = px_cache_mode.DeviceCoordinateCache # XXX: WARNING item caching seems to only be useful # if we don't re-generate the entire QPainterPath every time # don't ever use this - it's a colossal nightmare of artefacts # and is disastrous for performance. - # QGraphicsItem.ItemCoordinateCache + # cache_mode.ItemCoordinateCache # TODO: still questions todo with coord-cacheing that we should # probably talk to a core dev about: # - if this makes trasform interactions slower (such as zooming) @@ -176,7 +178,7 @@ class FlowGraphic(pg.GraphicsObject): @cm def reset_cache(self) -> None: try: - none = QGraphicsItem.NoCache + none = px_cache_mode.NoCache log.debug( f'{self._name} -> CACHE DISABLE: {none}' ) diff --git a/piker/ui/_dataviz.py b/piker/ui/_dataviz.py index cf3d5509..36251e48 100644 --- a/piker/ui/_dataviz.py +++ b/piker/ui/_dataviz.py @@ -40,8 +40,8 @@ from numpy import ( ndarray, ) import pyqtgraph as pg -from PyQt5.QtCore import QLineF +from piker.ui.qt import QLineF from ..data._sharedmem import ( ShmArray, ) diff --git a/piker/ui/_display.py b/piker/ui/_display.py index 5cb89f54..46e1b922 100644 --- a/piker/ui/_display.py +++ b/piker/ui/_display.py @@ -57,6 +57,7 @@ from piker.toolz import ( Profiler, ) from piker.log import get_logger +from piker import config # from ..data._source import tf_in_1s from ._axes import YAxisLabel from ._chart import ( @@ -1231,6 +1232,8 @@ async def link_views_with_region( # region.sigRegionChangeFinished.connect(update_pi_from_region) +# NOTE: default is set to 60 FPS until the runtime delivers the +# discoverd hw value below. _quote_throttle_rate: int = 60 - 6 @@ -1272,26 +1275,54 @@ async def display_symbol_data( # TODO: ctl over update loop's maximum frequency. # - load this from a config.toml! # - allow dyanmic configuration from chart UI? + ( + conf, + path, + ) = config.load() + ui_conf: dict = conf['ui'] + global _quote_throttle_rate from ._window import main_window - display_rate = main_window().current_screen().refreshRate() - _quote_throttle_rate = floor(display_rate) - 6 + + display_rate: int = floor( + main_window().current_screen().refreshRate() + ) - 6 + + mx_redraw_rate: int = ui_conf.get( + 'max_redraw_rate', + _quote_throttle_rate, + ) + + if mx_redraw_rate < display_rate: + log.info( + 'Down-throttling redraw rate to config setting\n' + f'display FPS: {display_rate}\n' + 'max_redraw_rate: {max_redraw_rate}\n' + ) + else: + _quote_throttle_rate = display_rate # TODO: we should be able to increase this if we use some # `mypyc` speedups elsewhere? 22ish seems to be the sweet # spot for single-feed chart. num_of_feeds = len(fqmes) - mx: int = 22 - if num_of_feeds > 1: - # there will be more ctx switches with more than 1 feed so we - # max throttle down a bit more. - mx = 16 + # if num_of_feeds > 1: + + # there will be more ctx switches with more than 1 feed so we + # max throttle down a bit more. + mx_per_feed: int = ( + ui_conf.get( + 'per_feed_redraw_rate', + mx_redraw_rate, + ) + or 16 + ) # limit to at least display's FPS # avoiding needless Qt-in-guest-mode context switches cycles_per_feed = min( round(_quote_throttle_rate/num_of_feeds), - mx, + mx_per_feed, ) feed: Feed diff --git a/piker/ui/_editors.py b/piker/ui/_editors.py index 5158c507..9aba7978 100644 --- a/piker/ui/_editors.py +++ b/piker/ui/_editors.py @@ -32,24 +32,21 @@ from pyqtgraph import ( QtCore, QtWidgets, ) -from PyQt5.QtCore import ( - QPointF, - QRectF, -) -from PyQt5.QtGui import ( - QColor, - QTransform, -) -from PyQt5.QtWidgets import ( - QGraphicsProxyWidget, - QGraphicsScene, - QLabel, -) from pyqtgraph import functions as fn import numpy as np from piker.types import Struct +from piker.ui.qt import ( + Qt, + QPointF, + QRectF, + QGraphicsProxyWidget, + QGraphicsScene, + QLabel, + QColor, + QTransform, +) from ._style import ( hcolor, _font, @@ -316,7 +313,9 @@ class SelectRect(QtWidgets.QGraphicsRectItem): self.setZValue(1e9) label = self._label = QLabel() - label.setTextFormat(0) # markdown + label.setTextFormat( + Qt.TextFormat.MarkdownText + ) label.setFont(_font.font) label.setMargin(0) label.setAlignment( diff --git a/piker/ui/_event.py b/piker/ui/_event.py index 27b98c97..44797fa4 100644 --- a/piker/ui/_event.py +++ b/piker/ui/_event.py @@ -23,28 +23,29 @@ from typing import Callable import trio from tractor.trionics import gather_contexts -from PyQt5 import QtCore -from PyQt5.QtCore import QEvent, pyqtBoundSignal -from PyQt5.QtWidgets import QWidget -from PyQt5.QtWidgets import ( - QGraphicsSceneMouseEvent as gs_mouse, -) +from piker.ui.qt import ( + QtCore, + QWidget, + QEvent, + keys, + gs_keys, + pyqtBoundSignal, +) from piker.types import Struct MOUSE_EVENTS = { - gs_mouse.GraphicsSceneMousePress, - gs_mouse.GraphicsSceneMouseRelease, - QEvent.MouseButtonPress, - QEvent.MouseButtonRelease, + gs_keys.GraphicsSceneMousePress, + gs_keys.GraphicsSceneMouseRelease, + keys.MouseButtonPress, + keys.MouseButtonRelease, # QtGui.QMouseEvent, } # TODO: maybe consider some constrained ints down the road? # https://pydantic-docs.helpmanual.io/usage/types/#constrained-types - class KeyboardMsg(Struct): '''Unpacked Qt keyboard event data. @@ -114,7 +115,10 @@ class EventRelay(QtCore.QObject): # something to do with Qt internals and calling the # parent handler? - if etype in {QEvent.KeyPress, QEvent.KeyRelease}: + if etype in { + QEvent.Type.KeyPress, + QEvent.Type.KeyRelease, + }: msg = KeyboardMsg( event=ev, @@ -160,7 +164,9 @@ class EventRelay(QtCore.QObject): async def open_event_stream( source_widget: QWidget, - event_types: set[QEvent] = {QEvent.KeyPress}, + event_types: set[QEvent] = { + QEvent.Type.KeyPress, + }, filter_auto_repeats: bool = True, ) -> trio.abc.ReceiveChannel: diff --git a/piker/ui/_exec.py b/piker/ui/_exec.py index 5b0655da..ba91e534 100644 --- a/piker/ui/_exec.py +++ b/piker/ui/_exec.py @@ -30,25 +30,22 @@ from typing import ( import platform import traceback -# Qt specific -import PyQt5 # noqa -from PyQt5.QtWidgets import ( - QWidget, - QMainWindow, - QApplication, -) -from PyQt5 import QtCore -from PyQt5.QtCore import ( - pyqtRemoveInputHook, - Qt, - QCoreApplication, -) import qdarkstyle from qdarkstyle import DarkPalette # import qdarkgraystyle # TODO: play with it import trio from outcome import Error +# Qt version-agnostic +from .qt import ( + QWidget, + QMainWindow, + QApplication, + QtCore, + pyqtRemoveInputHook, + Qt, + QCoreApplication, +) from ..service import ( maybe_open_pikerd, get_runtime_vars, @@ -150,7 +147,7 @@ def run_qtractor( # load dark theme stylesheet = qdarkstyle.load_stylesheet( - qt_api='pyqt5', + qt_api='pyqt6', palette=DarkPalette, ) app.setStyleSheet(stylesheet) diff --git a/piker/ui/_forms.py b/piker/ui/_forms.py index de64747e..504dd418 100644 --- a/piker/ui/_forms.py +++ b/piker/ui/_forms.py @@ -28,9 +28,15 @@ from typing import ( ) import trio -from PyQt5 import QtGui -from PyQt5.QtCore import QSize, QModelIndex, Qt, QEvent -from PyQt5.QtWidgets import ( + +from piker.ui.qt import ( + keys, + size_policy, + QtGui, + QSize, + QModelIndex, + Qt, + QEvent, QWidget, QLabel, QComboBox, @@ -39,7 +45,6 @@ from PyQt5.QtWidgets import ( QVBoxLayout, QFormLayout, QProgressBar, - QSizePolicy, QStyledItemDelegate, QStyleOptionViewItem, ) @@ -71,14 +76,14 @@ class Edit(QLineEdit): if width_in_chars: self._chars = int(width_in_chars) - x_size_policy = QSizePolicy.Fixed + x_size_policy = size_policy.Fixed else: # chart count which will be used to calculate # width of input field. self._chars: int = 6 # fit to surroundingn frame width - x_size_policy = QSizePolicy.Expanding + x_size_policy = size_policy.Expanding super().__init__(parent) @@ -86,7 +91,7 @@ class Edit(QLineEdit): # https://doc.qt.io/qt-5/qsizepolicy.html#Policy-enum self.setSizePolicy( x_size_policy, - QSizePolicy.Fixed, + size_policy.Fixed, ) self.setFont(font.font) @@ -180,11 +185,13 @@ class Selection(QComboBox): self._items: dict[str, int] = {} super().__init__(parent=parent) - self.setSizeAdjustPolicy(QComboBox.AdjustToContents) + self.setSizeAdjustPolicy( + QComboBox.SizeAdjustPolicy.AdjustToContents, + ) # make line edit expand to surrounding frame self.setSizePolicy( - QSizePolicy.Expanding, - QSizePolicy.Fixed, + size_policy.Expanding, + size_policy.Fixed, ) view = self.view() view.setUniformItemSizes(True) @@ -308,8 +315,8 @@ class FieldsForm(QWidget): # size it as we specify self.setSizePolicy( - QSizePolicy.Expanding, - QSizePolicy.Expanding, + size_policy.Expanding, + size_policy.Expanding, ) # XXX: not sure why we have to create this here exactly @@ -416,8 +423,8 @@ class FieldsForm(QWidget): select.set_items(values) self.setSizePolicy( - QSizePolicy.Fixed, - QSizePolicy.Fixed, + size_policy.Fixed, + size_policy.Fixed, ) select.show() self.form.addRow(label, select) @@ -437,7 +444,10 @@ async def handle_field_input( async for kbmsg in recv_chan: - if kbmsg.etype in {QEvent.KeyPress, QEvent.KeyRelease}: + if kbmsg.etype in { + keys.KeyPress, + keys.KeyRelease, + }: event, etype, key, mods, txt = kbmsg.to_tuple() print(f'key: {kbmsg.key}, mods: {kbmsg.mods}, txt: {kbmsg.txt}') diff --git a/piker/ui/_icons.py b/piker/ui/_icons.py index feb0cbb6..c42a93b7 100644 --- a/piker/ui/_icons.py +++ b/piker/ui/_icons.py @@ -15,15 +15,18 @@ # along with this program. If not, see . ''' -``QIcon`` hackery. +`QIcon` hackery. + +Mostly dynamically loading pixmaps for use with `QGraphicsScene`. ''' -from PyQt5.QtWidgets import QStyle -from PyQt5.QtGui import ( - QIcon, QPixmap, QColor +from piker.ui.qt import ( + QSize, + QStyle, + QIcon, + QPixmap, + QColor, ) -from PyQt5.QtCore import QSize - from ._style import hcolor # https://www.pythonguis.com/faq/built-in-qicons-pyqt/ @@ -44,7 +47,8 @@ def mk_icons( size: QSize, ) -> dict[str, QIcon]: - '''This helper is indempotent. + ''' + This helper is indempotent. ''' global _icons, _icon_names @@ -56,7 +60,11 @@ def mk_icons( # load account selection using current style for name, icon_name in _icon_names.items(): - stdpixmap = getattr(QStyle, icon_name) + stdpixmap = getattr( + # https://www.pythonguis.com/faq/built-in-qicons-pyqt/ + QStyle.StandardPixmap, # pyqt/pyside6 + icon_name, + ) stdicon = style.standardIcon(stdpixmap) pixmap = stdicon.pixmap(size) diff --git a/piker/ui/_interaction.py b/piker/ui/_interaction.py index 7c710506..9bd48139 100644 --- a/piker/ui/_interaction.py +++ b/piker/ui/_interaction.py @@ -36,23 +36,21 @@ import pyqtgraph as pg # this down the road.. Bo from pyqtgraph.GraphicsScene import mouseEvents as mevs # from pyqtgraph.GraphicsScene.mouseEvents import MouseDragEvent -from PyQt5.QtWidgets import QGraphicsSceneMouseEvent as gs_mouse -from PyQt5.QtGui import ( - QWheelEvent, -) -from PyQt5.QtCore import ( - Qt, - QEvent, -) from pyqtgraph import ( ViewBox, Point, QtCore, + functions as fn, ) -from pyqtgraph import functions as fn import numpy as np import trio +from piker.ui.qt import ( + QWheelEvent, + QGraphicsSceneMouseEvent as gs_mouse, + Qt, + QEvent, +) from ..log import get_logger from ..toolz import ( Profiler, @@ -81,22 +79,22 @@ if TYPE_CHECKING: log = get_logger(__name__) NUMBER_LINE = { - Qt.Key_1, - Qt.Key_2, - Qt.Key_3, - Qt.Key_4, - Qt.Key_5, - Qt.Key_6, - Qt.Key_7, - Qt.Key_8, - Qt.Key_9, - Qt.Key_0, + Qt.Key.Key_1, + Qt.Key.Key_2, + Qt.Key.Key_3, + Qt.Key.Key_4, + Qt.Key.Key_5, + Qt.Key.Key_6, + Qt.Key.Key_7, + Qt.Key.Key_8, + Qt.Key.Key_9, + Qt.Key.Key_0, } ORDER_MODE = { - Qt.Key_A, - Qt.Key_F, - Qt.Key_D, + Qt.Key.Key_A, + Qt.Key.Key_F, + Qt.Key.Key_D, } diff --git a/piker/ui/_l1.py b/piker/ui/_l1.py index 23162c70..8d29d90c 100644 --- a/piker/ui/_l1.py +++ b/piker/ui/_l1.py @@ -21,9 +21,12 @@ Double auction top-of-book (L1) graphics. from typing import Tuple import pyqtgraph as pg -from PyQt5 import QtCore, QtGui -from PyQt5.QtCore import QPointF +from piker.ui.qt import ( + QPointF, + QtCore, + QtGui, +) from ._axes import YAxisLabel from ._style import hcolor from ._pg_overrides import PlotItem diff --git a/piker/ui/_label.py b/piker/ui/_label.py index 1e010f18..0e90b7fe 100644 --- a/piker/ui/_label.py +++ b/piker/ui/_label.py @@ -25,10 +25,17 @@ from typing import ( ) import pyqtgraph as pg -from PyQt5 import QtGui, QtWidgets -from PyQt5.QtWidgets import QLabel, QSizePolicy -from PyQt5.QtCore import QPointF, QRectF, Qt +from piker.ui.qt import ( + px_cache_mode, + QtGui, + QtWidgets, + QLabel, + size_policy, + QPointF, + QRectF, + Qt, +) from ._style import ( DpiAwareFont, hcolor, @@ -78,7 +85,7 @@ class Label: self._x_offset = x_offset txt = self.txt = QtWidgets.QGraphicsTextItem(parent=parent) - txt.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache) + txt.setCacheMode(px_cache_mode.DeviceCoordinateCache) vb.scene().addItem(txt) @@ -103,7 +110,7 @@ class Label: self._anchor_func = self.txt.pos().x # not sure if this makes a diff - self.txt.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache) + self.txt.setCacheMode(px_cache_mode.DeviceCoordinateCache) # TODO: edit and selection support # https://doc.qt.io/qt-5/qt.html#TextInteractionFlag-enum @@ -299,12 +306,14 @@ class FormatLabel(QLabel): """ ) self.setFont(_font.font) - self.setTextFormat(Qt.MarkdownText) # markdown + self.setTextFormat( + Qt.TextFormat.MarkdownText + ) self.setMargin(0) self.setSizePolicy( - QSizePolicy.Expanding, - QSizePolicy.Expanding, + size_policy.Expanding, + size_policy.Expanding, ) self.setAlignment( Qt.AlignVCenter | Qt.AlignLeft diff --git a/piker/ui/_lines.py b/piker/ui/_lines.py index a2ea5331..e1b6d3ed 100644 --- a/piker/ui/_lines.py +++ b/piker/ui/_lines.py @@ -27,20 +27,22 @@ from typing import ( ) import pyqtgraph as pg -from pyqtgraph import Point, functions as fn -from PyQt5 import ( +from pyqtgraph import ( + Point, + functions as fn, +) + +from piker.ui.qt import ( + px_cache_mode, QtCore, QtGui, -) -from PyQt5.QtWidgets import ( QGraphicsPathItem, QStyleOptionGraphicsItem, QGraphicsItem, QGraphicsScene, QWidget, + QPointF, ) -from PyQt5.QtCore import QPointF - from ._annotate import LevelMarker from ._anchors import ( vbr_left, @@ -140,7 +142,9 @@ class LevelLine(pg.InfiniteLine): self._right_end_sc: float = 0 # use px caching - self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) + self.setCacheMode( + px_cache_mode.DeviceCoordinateCache + ) def txt_offsets(self) -> tuple[int, int]: return 0, 0 @@ -211,7 +215,7 @@ class LevelLine(pg.InfiniteLine): ) -> None: if not called_from_on_pos_change: - last = self.value() + last: float = self.value() # if the position hasn't changed then ``.update_labels()`` # will not be called by a non-triggered `.on_pos_change()`, diff --git a/piker/ui/_ohlc.py b/piker/ui/_ohlc.py index c43926a1..34fa3362 100644 --- a/piker/ui/_ohlc.py +++ b/piker/ui/_ohlc.py @@ -20,16 +20,14 @@ Super fast OHLC sampling graphics types. from __future__ import annotations import numpy as np -from PyQt5 import ( + +from piker.ui.qt import ( QtGui, QtWidgets, -) -from PyQt5.QtCore import ( + QPainterPath, QLineF, QRectF, ) -from PyQt5.QtGui import QPainterPath - from ._curve import FlowGraphic from ..toolz import ( Profiler, diff --git a/piker/ui/_remote_ctl.py b/piker/ui/_remote_ctl.py index 0dddc5c5..ccc8d6e9 100644 --- a/piker/ui/_remote_ctl.py +++ b/piker/ui/_remote_ctl.py @@ -38,14 +38,14 @@ from tractor import ( Context, MsgStream, ) -from PyQt5.QtWidgets import ( - QGraphicsItem, -) from piker.log import get_logger from piker.types import Struct from piker.service import find_service from piker.brokers import SymbolNotFound +from piker.ui.qt import ( + QGraphicsItem, +) from ._display import DisplayState from ._interaction import ChartView from ._editors import SelectRect diff --git a/piker/ui/_render.py b/piker/ui/_render.py index bd3d1757..64fad999 100644 --- a/piker/ui/_render.py +++ b/piker/ui/_render.py @@ -30,8 +30,8 @@ from typing import ( import msgspec import numpy as np import pyqtgraph as pg -from PyQt5.QtGui import QPainterPath +from piker.ui.qt import QPainterPath from ..data._formatters import ( IncrementalFormatter, ) diff --git a/piker/ui/_search.py b/piker/ui/_search.py index d6af5535..16b25a46 100644 --- a/piker/ui/_search.py +++ b/piker/ui/_search.py @@ -48,27 +48,24 @@ from pprint import pformat from rapidfuzz import process as fuzzy import trio from trio_typing import TaskStatus -from PyQt5 import QtCore -from PyQt5 import QtWidgets -from PyQt5.QtCore import ( + +from piker.ui.qt import ( + size_policy, + align_flag, Qt, + QtCore, + QtWidgets, QModelIndex, QItemSelectionModel, -) -from PyQt5.QtGui import ( # QLayout, QStandardItem, QStandardItemModel, -) -from PyQt5.QtWidgets import ( QWidget, QTreeView, # QListWidgetItem, # QAbstractScrollArea, # QStyledItemDelegate, ) - - from ..log import get_logger from ._style import ( _font, @@ -129,8 +126,8 @@ class CompleterView(QTreeView): # ux settings self.setSizePolicy( - QtWidgets.QSizePolicy.Expanding, - QtWidgets.QSizePolicy.Expanding, + size_policy.Expanding, + size_policy.Expanding, ) self.setItemsExpandable(True) self.setExpandsOnDoubleClick(False) @@ -567,8 +564,8 @@ class SearchWidget(QtWidgets.QWidget): # size it as we specify self.setSizePolicy( - QtWidgets.QSizePolicy.Fixed, - QtWidgets.QSizePolicy.Fixed, + size_policy.Fixed, + size_policy.Fixed, ) self.godwidget = godwidget @@ -592,14 +589,16 @@ class SearchWidget(QtWidgets.QWidget): }} """ ) - label.setTextFormat(3) # markdown + label.setTextFormat( + Qt.TextFormat.MarkdownText + ) label.setFont(_font.font) label.setMargin(4) label.setText("search:") label.show() label.setAlignment( - QtCore.Qt.AlignVCenter - | QtCore.Qt.AlignLeft + align_flag.AlignVCenter + | align_flag.AlignLeft ) self.bar_hbox.addWidget(label) @@ -617,9 +616,17 @@ class SearchWidget(QtWidgets.QWidget): self.vbox.addLayout(self.bar_hbox) - self.vbox.setAlignment(self.bar, Qt.AlignTop | Qt.AlignRight) + self.vbox.setAlignment( + self.bar, + align_flag.AlignTop + | align_flag.AlignRight, + ) self.vbox.addWidget(self.bar.view) - self.vbox.setAlignment(self.view, Qt.AlignTop | Qt.AlignLeft) + self.vbox.setAlignment( + self.view, + align_flag.AlignTop + | align_flag.AlignLeft, + ) def focus(self) -> None: self.show() diff --git a/piker/ui/_style.py b/piker/ui/_style.py index 2d17b62d..302d9d30 100644 --- a/piker/ui/_style.py +++ b/piker/ui/_style.py @@ -22,10 +22,14 @@ from typing import Dict import math import pyqtgraph as pg -from PyQt5 import QtCore, QtGui -from PyQt5.QtCore import Qt, QCoreApplication from qdarkstyle import DarkPalette +from .qt import ( + QtCore, + QtGui, + Qt, + QCoreApplication, +) from ..log import get_logger from .. import config diff --git a/piker/ui/_window.py b/piker/ui/_window.py index 0fc87c24..a15ecd24 100644 --- a/piker/ui/_window.py +++ b/piker/ui/_window.py @@ -27,16 +27,14 @@ from typing import ( ) import uuid -from PyQt5 import QtCore -from PyQt5.QtWidgets import ( +from piker.ui.qt import ( + Qt, + QtCore, QWidget, QMainWindow, QApplication, QLabel, QStatusBar, -) - -from PyQt5.QtGui import ( QScreen, QCloseEvent, ) @@ -197,7 +195,9 @@ class MainWindow(QMainWindow): """ # font-size : {font_size}px; ) - label.setTextFormat(3) # markdown + label.setTextFormat( + Qt.TextFormat.MarkdownText + ) label.setFont(_font_small.font) label.setMargin(2) label.setAlignment( diff --git a/piker/ui/order_mode.py b/piker/ui/order_mode.py index ea96e97a..d5720e8a 100644 --- a/piker/ui/order_mode.py +++ b/piker/ui/order_mode.py @@ -34,7 +34,6 @@ import uuid from bidict import bidict import tractor import trio -from PyQt5.QtCore import Qt from piker import config from piker.accounting import ( @@ -59,6 +58,7 @@ from piker.data import ( ) from piker.types import Struct from piker.log import get_logger +from piker.ui.qt import Qt from ._editors import LineEditor, ArrowEditor from ._lines import order_line, LevelLine from ._position import ( @@ -358,7 +358,7 @@ class OrderMode: send_msg: bool = True, order: Order | None = None, - ) -> Dialog | None: + ) -> Dialog|None: ''' Send execution order to EMS return a level line to represent the order on a chart. @@ -494,7 +494,7 @@ class OrderMode: uuid: str, order: Order | None = None, - ) -> Dialog: + ) -> Dialog | None: ''' Order submitted status event handler. @@ -515,6 +515,11 @@ class OrderMode: # if an order msg is provided update the line # **from** that msg. if order: + if order.price <= 0: + log.error(f'Order has 0 price, cancelling..\n{order}') + self.cancel_orders([order.oid]) + return None + line.set_level(order.price) self.on_level_change_update_next_order_info( level=order.price, @@ -1013,8 +1018,13 @@ async def process_trade_msg( ) -> tuple[Dialog, Status]: - fmsg = pformat(msg) - log.debug(f'Received order msg:\n{fmsg}') + # TODO: obvi once we're parsing to native struct instances we can + # drop the `pformat()` call Bo + fmtmsg: Struct | dict = msg + if not isinstance(msg, Struct): + fmtmsg: str = pformat(msg) + + log.debug(f'Received order msg:\n{fmtmsg}') name = msg['name'] if name in ( @@ -1030,7 +1040,7 @@ async def process_trade_msg( ): log.info( f'Loading position for `{fqme}`:\n' - f'{fmsg}' + f'{fmtmsg}' ) tracker = mode.trackers[msg['account']] tracker.live_pp.update_from_msg(msg) @@ -1072,7 +1082,7 @@ async def process_trade_msg( elif order.action != 'cancel': log.warning( - f'received msg for untracked dialog:\n{fmsg}' + f'received msg for untracked dialog:\n{fmtmsg}' ) assert msg.resp in ('open', 'dark_open'), f'Unknown msg: {msg}' @@ -1139,7 +1149,7 @@ async def process_trade_msg( req={'exec_mode': 'dark'}, ): # TODO: UX for a "pending" clear/live order - log.info(f'Dark order triggered for {fmsg}') + log.info(f'Dark order triggered for {fmtmsg}') case Status( resp='triggered',