Port all `.ui*` submods to new `.ui.qt` imports

This also officially moves the code base to using `PyQt6` including all
necessary reference changes and enum namespace path moves.

Also includes a small `.ui.order_mode` fix to cancel any
`Order.price <= 0` and a name error fix with logging using `msg`,
which is already used for the input order msg..
pyqt6
Tyler Goodlet 2024-05-01 14:33:10 -04:00
parent d0170982bf
commit 1fd8654ca5
26 changed files with 327 additions and 216 deletions

View File

@ -14,9 +14,8 @@
# You should have received a copy of the GNU Affero General Public License # 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/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
""" '''
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
"""

View File

@ -21,8 +21,10 @@ Anchor funtions for UI placement of annotions.
from __future__ import annotations from __future__ import annotations
from typing import Callable, TYPE_CHECKING from typing import Callable, TYPE_CHECKING
from PyQt5.QtCore import QPointF from piker.ui.qt import (
from PyQt5.QtWidgets import QGraphicsPathItem QPointF,
QGraphicsPathItem,
)
if TYPE_CHECKING: if TYPE_CHECKING:
from ._chart import ChartPlotWidget from ._chart import ChartPlotWidget

View File

@ -20,12 +20,22 @@ Annotations for ur faces.
""" """
from typing import Callable from typing import Callable
from PyQt5 import QtCore, QtGui, QtWidgets from pyqtgraph import (
from PyQt5.QtCore import QPointF, QRectF Point,
from PyQt5.QtWidgets import QGraphicsPathItem functions as fn,
from pyqtgraph import Point, functions as fn, Color Color,
)
import numpy as np import numpy as np
from piker.ui.qt import (
QtCore,
QtGui,
QtWidgets,
QPointF,
QRectF,
QGraphicsPathItem,
)
def mk_marker_path( def mk_marker_path(

View File

@ -21,9 +21,11 @@ Main app startup and run.
from functools import partial from functools import partial
from types import ModuleType from types import ModuleType
from PyQt5.QtCore import QEvent
import trio import trio
from piker.ui.qt import (
QEvent,
)
from ..service import maybe_spawn_brokerd from ..service import maybe_spawn_brokerd
from . import _event from . import _event
from ._exec import run_qtractor from ._exec import run_qtractor

View File

@ -25,9 +25,16 @@ from math import floor
import polars as pl import polars as pl
import pyqtgraph as pg 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 . import _pg_overrides as pgo
from ..accounting._mktinfo import float_digits from ..accounting._mktinfo import float_digits
from ._label import Label from ._label import Label
@ -414,11 +421,15 @@ class AxisLabel(pg.GraphicsObject):
super().__init__() super().__init__()
self.setParentItem(parent) self.setParentItem(parent)
self.setFlag(self.ItemIgnoresTransformations) self.setFlag(
self.GraphicsItemFlag.ItemIgnoresTransformations
)
self.setZValue(100) self.setZValue(100)
# XXX: pretty sure this is faster # XXX: pretty sure this is faster
self.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache) self.setCacheMode(
px_cache_mode.DeviceCoordinateCache
)
self._parent = parent self._parent = parent
@ -555,21 +566,14 @@ class AxisLabel(pg.GraphicsObject):
return (self.rect.width(), self.rect.height()) 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): class XAxisLabel(AxisLabel):
_x_margin = 8 _x_margin = 8
text_flags = ( text_flags = (
QtCore.Qt.TextDontClip align_flag.AlignCenter
| QtCore.Qt.AlignCenter | txt_flag.TextDontClip
) )
def size_hint(self) -> tuple[float, float]: def size_hint(self) -> tuple[float, float]:
@ -626,10 +630,10 @@ class YAxisLabel(AxisLabel):
_y_margin: int = 4 _y_margin: int = 4
text_flags = ( text_flags = (
QtCore.Qt.AlignLeft align_flag.AlignLeft
# QtCore.Qt.AlignHCenter | align_flag.AlignVCenter
| QtCore.Qt.AlignVCenter # | align_flag.AlignHCenter
| QtCore.Qt.TextDontClip | txt_flag.TextDontClip
) )
def __init__( def __init__(

View File

@ -28,22 +28,20 @@ from typing import (
TYPE_CHECKING, TYPE_CHECKING,
) )
from PyQt5 import QtCore, QtWidgets import pyqtgraph as pg
from PyQt5.QtCore import ( import trio
from piker.ui.qt import (
QtCore,
QtWidgets,
Qt, Qt,
QLineF, QLineF,
# QPointF,
)
from PyQt5.QtWidgets import (
QFrame, QFrame,
QWidget, QWidget,
QHBoxLayout, QHBoxLayout,
QVBoxLayout, QVBoxLayout,
QSplitter, QSplitter,
) )
import pyqtgraph as pg
import trio
from ._axes import ( from ._axes import (
DynamicDateAxis, DynamicDateAxis,
PriceAxis, PriceAxis,
@ -570,8 +568,8 @@ class LinkedSplits(QWidget):
# style? # style?
self.chart.setFrameStyle( self.chart.setFrameStyle(
QFrame.StyledPanel | QFrame.Shape.StyledPanel |
QFrame.Plain QFrame.Shadow.Plain
) )
return self.chart return self.chart
@ -689,8 +687,8 @@ class LinkedSplits(QWidget):
cpw.plotItem.vb.linked = self cpw.plotItem.vb.linked = self
cpw.setFrameStyle( cpw.setFrameStyle(
QtWidgets.QFrame.StyledPanel QFrame.Shape.StyledPanel
# | QtWidgets.QFrame.Plain # | QFrame.Shadow.Plain
) )
# don't show the little "autoscale" A label. # don't show the little "autoscale" A label.

View File

@ -28,9 +28,14 @@ from typing import (
import inspect import inspect
import numpy as np import numpy as np
import pyqtgraph as pg 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 ( from ._style import (
_xaxis_at, _xaxis_at,
hcolor, hcolor,
@ -104,7 +109,9 @@ class LineDot(pg.CurvePoint):
dot.setParentItem(self) dot.setParentItem(self)
# keep a static size # keep a static size
self.setFlag(self.ItemIgnoresTransformations) self.setFlag(
self.GraphicsItemFlag.ItemIgnoresTransformations
)
def event( def event(
self, self,
@ -424,10 +431,10 @@ class Cursor(pg.GraphicsObject):
# vertical and horizonal lines and a y-axis label # vertical and horizonal lines and a y-axis label
vl = plot.addLine(x=0, pen=self.lines_pen, movable=False) 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 = plot.addLine(y=0, pen=self.lines_pen, movable=False)
hl.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache) hl.setCacheMode(px_cache_mode.DeviceCoordinateCache)
hl.hide() hl.hide()
yl = YAxisLabel( yl = YAxisLabel(
@ -511,7 +518,10 @@ class Cursor(pg.GraphicsObject):
plot=chart plot=chart
) )
chart.addItem(cursor) chart.addItem(cursor)
self.graphics[chart].setdefault('cursors', []).append(cursor) self.graphics[chart].setdefault(
'cursors',
[],
).append(cursor)
return cursor return cursor
def mouseAction( def mouseAction(

View File

@ -19,20 +19,21 @@ Fast, smooth, sexy curves.
""" """
from contextlib import contextmanager as cm from contextlib import contextmanager as cm
from enum import EnumType
from typing import Callable from typing import Callable
import numpy as np import numpy as np
import pyqtgraph as pg import pyqtgraph as pg
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QGraphicsItem from piker.ui.qt import (
from PyQt5.QtCore import ( QtWidgets,
QGraphicsItem,
Qt, Qt,
QLineF, QLineF,
QRectF, QRectF,
)
from PyQt5.QtGui import (
QPainter, QPainter,
QPainterPath, QPainterPath,
px_cache_mode,
) )
from ._style import hcolor from ._style import hcolor
from ..log import get_logger from ..log import get_logger
@ -42,15 +43,16 @@ from ..toolz.profile import (
ms_slower_then, ms_slower_then,
) )
log = get_logger(__name__) log = get_logger(__name__)
pen_style: EnumType = Qt.PenStyle
_line_styles: dict[str, int] = { _line_styles: dict[str, int] = {
'solid': Qt.PenStyle.SolidLine, 'solid': pen_style.SolidLine,
'dash': Qt.PenStyle.DashLine, 'dash': pen_style.DashLine,
'dot': Qt.PenStyle.DotLine, 'dot': pen_style.DotLine,
'dashdot': Qt.PenStyle.DashDotLine, 'dashdot': pen_style.DashDotLine,
} }
@ -69,12 +71,12 @@ class FlowGraphic(pg.GraphicsObject):
# XXX-NOTE-XXX: graphics caching B) # XXX-NOTE-XXX: graphics caching B)
# see explanation for different caching modes: # see explanation for different caching modes:
# https://stackoverflow.com/a/39410081 # 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 # XXX: WARNING item caching seems to only be useful
# if we don't re-generate the entire QPainterPath every time # if we don't re-generate the entire QPainterPath every time
# don't ever use this - it's a colossal nightmare of artefacts # don't ever use this - it's a colossal nightmare of artefacts
# and is disastrous for performance. # and is disastrous for performance.
# QGraphicsItem.ItemCoordinateCache # cache_mode.ItemCoordinateCache
# TODO: still questions todo with coord-cacheing that we should # TODO: still questions todo with coord-cacheing that we should
# probably talk to a core dev about: # probably talk to a core dev about:
# - if this makes trasform interactions slower (such as zooming) # - if this makes trasform interactions slower (such as zooming)
@ -176,7 +178,7 @@ class FlowGraphic(pg.GraphicsObject):
@cm @cm
def reset_cache(self) -> None: def reset_cache(self) -> None:
try: try:
none = QGraphicsItem.NoCache none = px_cache_mode.NoCache
log.debug( log.debug(
f'{self._name} -> CACHE DISABLE: {none}' f'{self._name} -> CACHE DISABLE: {none}'
) )

View File

@ -40,8 +40,8 @@ from numpy import (
ndarray, ndarray,
) )
import pyqtgraph as pg import pyqtgraph as pg
from PyQt5.QtCore import QLineF
from piker.ui.qt import QLineF
from ..data._sharedmem import ( from ..data._sharedmem import (
ShmArray, ShmArray,
) )

View File

@ -57,6 +57,7 @@ from piker.toolz import (
Profiler, Profiler,
) )
from piker.log import get_logger from piker.log import get_logger
from piker import config
# from ..data._source import tf_in_1s # from ..data._source import tf_in_1s
from ._axes import YAxisLabel from ._axes import YAxisLabel
from ._chart import ( from ._chart import (
@ -1231,6 +1232,8 @@ async def link_views_with_region(
# region.sigRegionChangeFinished.connect(update_pi_from_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 _quote_throttle_rate: int = 60 - 6
@ -1272,26 +1275,54 @@ async def display_symbol_data(
# TODO: ctl over update loop's maximum frequency. # TODO: ctl over update loop's maximum frequency.
# - load this from a config.toml! # - load this from a config.toml!
# - allow dyanmic configuration from chart UI? # - allow dyanmic configuration from chart UI?
(
conf,
path,
) = config.load()
ui_conf: dict = conf['ui']
global _quote_throttle_rate global _quote_throttle_rate
from ._window import main_window 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 # TODO: we should be able to increase this if we use some
# `mypyc` speedups elsewhere? 22ish seems to be the sweet # `mypyc` speedups elsewhere? 22ish seems to be the sweet
# spot for single-feed chart. # spot for single-feed chart.
num_of_feeds = len(fqmes) num_of_feeds = len(fqmes)
mx: int = 22 # if num_of_feeds > 1:
if num_of_feeds > 1:
# there will be more ctx switches with more than 1 feed so we # there will be more ctx switches with more than 1 feed so we
# max throttle down a bit more. # max throttle down a bit more.
mx = 16 mx_per_feed: int = (
ui_conf.get(
'per_feed_redraw_rate',
mx_redraw_rate,
)
or 16
)
# limit to at least display's FPS # limit to at least display's FPS
# avoiding needless Qt-in-guest-mode context switches # avoiding needless Qt-in-guest-mode context switches
cycles_per_feed = min( cycles_per_feed = min(
round(_quote_throttle_rate/num_of_feeds), round(_quote_throttle_rate/num_of_feeds),
mx, mx_per_feed,
) )
feed: Feed feed: Feed

View File

@ -32,24 +32,21 @@ from pyqtgraph import (
QtCore, QtCore,
QtWidgets, 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 from pyqtgraph import functions as fn
import numpy as np import numpy as np
from piker.types import Struct from piker.types import Struct
from piker.ui.qt import (
Qt,
QPointF,
QRectF,
QGraphicsProxyWidget,
QGraphicsScene,
QLabel,
QColor,
QTransform,
)
from ._style import ( from ._style import (
hcolor, hcolor,
_font, _font,
@ -316,7 +313,9 @@ class SelectRect(QtWidgets.QGraphicsRectItem):
self.setZValue(1e9) self.setZValue(1e9)
label = self._label = QLabel() label = self._label = QLabel()
label.setTextFormat(0) # markdown label.setTextFormat(
Qt.TextFormat.MarkdownText
)
label.setFont(_font.font) label.setFont(_font.font)
label.setMargin(0) label.setMargin(0)
label.setAlignment( label.setAlignment(

View File

@ -23,28 +23,29 @@ from typing import Callable
import trio import trio
from tractor.trionics import gather_contexts 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 from piker.types import Struct
MOUSE_EVENTS = { MOUSE_EVENTS = {
gs_mouse.GraphicsSceneMousePress, gs_keys.GraphicsSceneMousePress,
gs_mouse.GraphicsSceneMouseRelease, gs_keys.GraphicsSceneMouseRelease,
QEvent.MouseButtonPress, keys.MouseButtonPress,
QEvent.MouseButtonRelease, keys.MouseButtonRelease,
# QtGui.QMouseEvent, # QtGui.QMouseEvent,
} }
# TODO: maybe consider some constrained ints down the road? # TODO: maybe consider some constrained ints down the road?
# https://pydantic-docs.helpmanual.io/usage/types/#constrained-types # https://pydantic-docs.helpmanual.io/usage/types/#constrained-types
class KeyboardMsg(Struct): class KeyboardMsg(Struct):
'''Unpacked Qt keyboard event data. '''Unpacked Qt keyboard event data.
@ -114,7 +115,10 @@ class EventRelay(QtCore.QObject):
# something to do with Qt internals and calling the # something to do with Qt internals and calling the
# parent handler? # parent handler?
if etype in {QEvent.KeyPress, QEvent.KeyRelease}: if etype in {
QEvent.Type.KeyPress,
QEvent.Type.KeyRelease,
}:
msg = KeyboardMsg( msg = KeyboardMsg(
event=ev, event=ev,
@ -160,7 +164,9 @@ class EventRelay(QtCore.QObject):
async def open_event_stream( async def open_event_stream(
source_widget: QWidget, source_widget: QWidget,
event_types: set[QEvent] = {QEvent.KeyPress}, event_types: set[QEvent] = {
QEvent.Type.KeyPress,
},
filter_auto_repeats: bool = True, filter_auto_repeats: bool = True,
) -> trio.abc.ReceiveChannel: ) -> trio.abc.ReceiveChannel:

View File

@ -30,25 +30,22 @@ from typing import (
import platform import platform
import traceback 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 import qdarkstyle
from qdarkstyle import DarkPalette from qdarkstyle import DarkPalette
# import qdarkgraystyle # TODO: play with it # import qdarkgraystyle # TODO: play with it
import trio import trio
from outcome import Error from outcome import Error
# Qt version-agnostic
from .qt import (
QWidget,
QMainWindow,
QApplication,
QtCore,
pyqtRemoveInputHook,
Qt,
QCoreApplication,
)
from ..service import ( from ..service import (
maybe_open_pikerd, maybe_open_pikerd,
get_runtime_vars, get_runtime_vars,
@ -150,7 +147,7 @@ def run_qtractor(
# load dark theme # load dark theme
stylesheet = qdarkstyle.load_stylesheet( stylesheet = qdarkstyle.load_stylesheet(
qt_api='pyqt5', qt_api='pyqt6',
palette=DarkPalette, palette=DarkPalette,
) )
app.setStyleSheet(stylesheet) app.setStyleSheet(stylesheet)

View File

@ -28,9 +28,15 @@ from typing import (
) )
import trio import trio
from PyQt5 import QtGui
from PyQt5.QtCore import QSize, QModelIndex, Qt, QEvent from piker.ui.qt import (
from PyQt5.QtWidgets import ( keys,
size_policy,
QtGui,
QSize,
QModelIndex,
Qt,
QEvent,
QWidget, QWidget,
QLabel, QLabel,
QComboBox, QComboBox,
@ -39,7 +45,6 @@ from PyQt5.QtWidgets import (
QVBoxLayout, QVBoxLayout,
QFormLayout, QFormLayout,
QProgressBar, QProgressBar,
QSizePolicy,
QStyledItemDelegate, QStyledItemDelegate,
QStyleOptionViewItem, QStyleOptionViewItem,
) )
@ -71,14 +76,14 @@ class Edit(QLineEdit):
if width_in_chars: if width_in_chars:
self._chars = int(width_in_chars) self._chars = int(width_in_chars)
x_size_policy = QSizePolicy.Fixed x_size_policy = size_policy.Fixed
else: else:
# chart count which will be used to calculate # chart count which will be used to calculate
# width of input field. # width of input field.
self._chars: int = 6 self._chars: int = 6
# fit to surroundingn frame width # fit to surroundingn frame width
x_size_policy = QSizePolicy.Expanding x_size_policy = size_policy.Expanding
super().__init__(parent) super().__init__(parent)
@ -86,7 +91,7 @@ class Edit(QLineEdit):
# https://doc.qt.io/qt-5/qsizepolicy.html#Policy-enum # https://doc.qt.io/qt-5/qsizepolicy.html#Policy-enum
self.setSizePolicy( self.setSizePolicy(
x_size_policy, x_size_policy,
QSizePolicy.Fixed, size_policy.Fixed,
) )
self.setFont(font.font) self.setFont(font.font)
@ -180,11 +185,13 @@ class Selection(QComboBox):
self._items: dict[str, int] = {} self._items: dict[str, int] = {}
super().__init__(parent=parent) super().__init__(parent=parent)
self.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.setSizeAdjustPolicy(
QComboBox.SizeAdjustPolicy.AdjustToContents,
)
# make line edit expand to surrounding frame # make line edit expand to surrounding frame
self.setSizePolicy( self.setSizePolicy(
QSizePolicy.Expanding, size_policy.Expanding,
QSizePolicy.Fixed, size_policy.Fixed,
) )
view = self.view() view = self.view()
view.setUniformItemSizes(True) view.setUniformItemSizes(True)
@ -308,8 +315,8 @@ class FieldsForm(QWidget):
# size it as we specify # size it as we specify
self.setSizePolicy( self.setSizePolicy(
QSizePolicy.Expanding, size_policy.Expanding,
QSizePolicy.Expanding, size_policy.Expanding,
) )
# XXX: not sure why we have to create this here exactly # XXX: not sure why we have to create this here exactly
@ -416,8 +423,8 @@ class FieldsForm(QWidget):
select.set_items(values) select.set_items(values)
self.setSizePolicy( self.setSizePolicy(
QSizePolicy.Fixed, size_policy.Fixed,
QSizePolicy.Fixed, size_policy.Fixed,
) )
select.show() select.show()
self.form.addRow(label, select) self.form.addRow(label, select)
@ -437,7 +444,10 @@ async def handle_field_input(
async for kbmsg in recv_chan: 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() event, etype, key, mods, txt = kbmsg.to_tuple()
print(f'key: {kbmsg.key}, mods: {kbmsg.mods}, txt: {kbmsg.txt}') print(f'key: {kbmsg.key}, mods: {kbmsg.mods}, txt: {kbmsg.txt}')

View File

@ -15,15 +15,18 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
''' '''
``QIcon`` hackery. `QIcon` hackery.
Mostly dynamically loading pixmaps for use with `QGraphicsScene`.
''' '''
from PyQt5.QtWidgets import QStyle from piker.ui.qt import (
from PyQt5.QtGui import ( QSize,
QIcon, QPixmap, QColor QStyle,
QIcon,
QPixmap,
QColor,
) )
from PyQt5.QtCore import QSize
from ._style import hcolor from ._style import hcolor
# https://www.pythonguis.com/faq/built-in-qicons-pyqt/ # https://www.pythonguis.com/faq/built-in-qicons-pyqt/
@ -44,7 +47,8 @@ def mk_icons(
size: QSize, size: QSize,
) -> dict[str, QIcon]: ) -> dict[str, QIcon]:
'''This helper is indempotent. '''
This helper is indempotent.
''' '''
global _icons, _icon_names global _icons, _icon_names
@ -56,7 +60,11 @@ def mk_icons(
# load account selection using current style # load account selection using current style
for name, icon_name in _icon_names.items(): 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) stdicon = style.standardIcon(stdpixmap)
pixmap = stdicon.pixmap(size) pixmap = stdicon.pixmap(size)

View File

@ -36,23 +36,21 @@ import pyqtgraph as pg
# this down the road.. Bo # this down the road.. Bo
from pyqtgraph.GraphicsScene import mouseEvents as mevs from pyqtgraph.GraphicsScene import mouseEvents as mevs
# from pyqtgraph.GraphicsScene.mouseEvents import MouseDragEvent # 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 ( from pyqtgraph import (
ViewBox, ViewBox,
Point, Point,
QtCore, QtCore,
functions as fn,
) )
from pyqtgraph import functions as fn
import numpy as np import numpy as np
import trio import trio
from piker.ui.qt import (
QWheelEvent,
QGraphicsSceneMouseEvent as gs_mouse,
Qt,
QEvent,
)
from ..log import get_logger from ..log import get_logger
from ..toolz import ( from ..toolz import (
Profiler, Profiler,
@ -81,22 +79,22 @@ if TYPE_CHECKING:
log = get_logger(__name__) log = get_logger(__name__)
NUMBER_LINE = { NUMBER_LINE = {
Qt.Key_1, Qt.Key.Key_1,
Qt.Key_2, Qt.Key.Key_2,
Qt.Key_3, Qt.Key.Key_3,
Qt.Key_4, Qt.Key.Key_4,
Qt.Key_5, Qt.Key.Key_5,
Qt.Key_6, Qt.Key.Key_6,
Qt.Key_7, Qt.Key.Key_7,
Qt.Key_8, Qt.Key.Key_8,
Qt.Key_9, Qt.Key.Key_9,
Qt.Key_0, Qt.Key.Key_0,
} }
ORDER_MODE = { ORDER_MODE = {
Qt.Key_A, Qt.Key.Key_A,
Qt.Key_F, Qt.Key.Key_F,
Qt.Key_D, Qt.Key.Key_D,
} }

View File

@ -21,9 +21,12 @@ Double auction top-of-book (L1) graphics.
from typing import Tuple from typing import Tuple
import pyqtgraph as pg 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 ._axes import YAxisLabel
from ._style import hcolor from ._style import hcolor
from ._pg_overrides import PlotItem from ._pg_overrides import PlotItem

View File

@ -25,10 +25,17 @@ from typing import (
) )
import pyqtgraph as pg 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 ( from ._style import (
DpiAwareFont, DpiAwareFont,
hcolor, hcolor,
@ -78,7 +85,7 @@ class Label:
self._x_offset = x_offset self._x_offset = x_offset
txt = self.txt = QtWidgets.QGraphicsTextItem(parent=parent) txt = self.txt = QtWidgets.QGraphicsTextItem(parent=parent)
txt.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache) txt.setCacheMode(px_cache_mode.DeviceCoordinateCache)
vb.scene().addItem(txt) vb.scene().addItem(txt)
@ -103,7 +110,7 @@ class Label:
self._anchor_func = self.txt.pos().x self._anchor_func = self.txt.pos().x
# not sure if this makes a diff # 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 # TODO: edit and selection support
# https://doc.qt.io/qt-5/qt.html#TextInteractionFlag-enum # https://doc.qt.io/qt-5/qt.html#TextInteractionFlag-enum
@ -299,12 +306,14 @@ class FormatLabel(QLabel):
""" """
) )
self.setFont(_font.font) self.setFont(_font.font)
self.setTextFormat(Qt.MarkdownText) # markdown self.setTextFormat(
Qt.TextFormat.MarkdownText
)
self.setMargin(0) self.setMargin(0)
self.setSizePolicy( self.setSizePolicy(
QSizePolicy.Expanding, size_policy.Expanding,
QSizePolicy.Expanding, size_policy.Expanding,
) )
self.setAlignment( self.setAlignment(
Qt.AlignVCenter | Qt.AlignLeft Qt.AlignVCenter | Qt.AlignLeft

View File

@ -27,20 +27,22 @@ from typing import (
) )
import pyqtgraph as pg import pyqtgraph as pg
from pyqtgraph import Point, functions as fn from pyqtgraph import (
from PyQt5 import ( Point,
functions as fn,
)
from piker.ui.qt import (
px_cache_mode,
QtCore, QtCore,
QtGui, QtGui,
)
from PyQt5.QtWidgets import (
QGraphicsPathItem, QGraphicsPathItem,
QStyleOptionGraphicsItem, QStyleOptionGraphicsItem,
QGraphicsItem, QGraphicsItem,
QGraphicsScene, QGraphicsScene,
QWidget, QWidget,
QPointF,
) )
from PyQt5.QtCore import QPointF
from ._annotate import LevelMarker from ._annotate import LevelMarker
from ._anchors import ( from ._anchors import (
vbr_left, vbr_left,
@ -140,7 +142,9 @@ class LevelLine(pg.InfiniteLine):
self._right_end_sc: float = 0 self._right_end_sc: float = 0
# use px caching # use px caching
self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) self.setCacheMode(
px_cache_mode.DeviceCoordinateCache
)
def txt_offsets(self) -> tuple[int, int]: def txt_offsets(self) -> tuple[int, int]:
return 0, 0 return 0, 0
@ -211,7 +215,7 @@ class LevelLine(pg.InfiniteLine):
) -> None: ) -> None:
if not called_from_on_pos_change: if not called_from_on_pos_change:
last = self.value() last: float = self.value()
# if the position hasn't changed then ``.update_labels()`` # if the position hasn't changed then ``.update_labels()``
# will not be called by a non-triggered `.on_pos_change()`, # will not be called by a non-triggered `.on_pos_change()`,

View File

@ -20,16 +20,14 @@ Super fast OHLC sampling graphics types.
from __future__ import annotations from __future__ import annotations
import numpy as np import numpy as np
from PyQt5 import (
from piker.ui.qt import (
QtGui, QtGui,
QtWidgets, QtWidgets,
) QPainterPath,
from PyQt5.QtCore import (
QLineF, QLineF,
QRectF, QRectF,
) )
from PyQt5.QtGui import QPainterPath
from ._curve import FlowGraphic from ._curve import FlowGraphic
from ..toolz import ( from ..toolz import (
Profiler, Profiler,

View File

@ -38,14 +38,14 @@ from tractor import (
Context, Context,
MsgStream, MsgStream,
) )
from PyQt5.QtWidgets import (
QGraphicsItem,
)
from piker.log import get_logger from piker.log import get_logger
from piker.types import Struct from piker.types import Struct
from piker.service import find_service from piker.service import find_service
from piker.brokers import SymbolNotFound from piker.brokers import SymbolNotFound
from piker.ui.qt import (
QGraphicsItem,
)
from ._display import DisplayState from ._display import DisplayState
from ._interaction import ChartView from ._interaction import ChartView
from ._editors import SelectRect from ._editors import SelectRect

View File

@ -30,8 +30,8 @@ from typing import (
import msgspec import msgspec
import numpy as np import numpy as np
import pyqtgraph as pg import pyqtgraph as pg
from PyQt5.QtGui import QPainterPath
from piker.ui.qt import QPainterPath
from ..data._formatters import ( from ..data._formatters import (
IncrementalFormatter, IncrementalFormatter,
) )

View File

@ -48,27 +48,24 @@ from pprint import pformat
from rapidfuzz import process as fuzzy from rapidfuzz import process as fuzzy
import trio import trio
from trio_typing import TaskStatus from trio_typing import TaskStatus
from PyQt5 import QtCore
from PyQt5 import QtWidgets from piker.ui.qt import (
from PyQt5.QtCore import ( size_policy,
align_flag,
Qt, Qt,
QtCore,
QtWidgets,
QModelIndex, QModelIndex,
QItemSelectionModel, QItemSelectionModel,
)
from PyQt5.QtGui import (
# QLayout, # QLayout,
QStandardItem, QStandardItem,
QStandardItemModel, QStandardItemModel,
)
from PyQt5.QtWidgets import (
QWidget, QWidget,
QTreeView, QTreeView,
# QListWidgetItem, # QListWidgetItem,
# QAbstractScrollArea, # QAbstractScrollArea,
# QStyledItemDelegate, # QStyledItemDelegate,
) )
from ..log import get_logger from ..log import get_logger
from ._style import ( from ._style import (
_font, _font,
@ -129,8 +126,8 @@ class CompleterView(QTreeView):
# ux settings # ux settings
self.setSizePolicy( self.setSizePolicy(
QtWidgets.QSizePolicy.Expanding, size_policy.Expanding,
QtWidgets.QSizePolicy.Expanding, size_policy.Expanding,
) )
self.setItemsExpandable(True) self.setItemsExpandable(True)
self.setExpandsOnDoubleClick(False) self.setExpandsOnDoubleClick(False)
@ -567,8 +564,8 @@ class SearchWidget(QtWidgets.QWidget):
# size it as we specify # size it as we specify
self.setSizePolicy( self.setSizePolicy(
QtWidgets.QSizePolicy.Fixed, size_policy.Fixed,
QtWidgets.QSizePolicy.Fixed, size_policy.Fixed,
) )
self.godwidget = godwidget 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.setFont(_font.font)
label.setMargin(4) label.setMargin(4)
label.setText("search:") label.setText("search:")
label.show() label.show()
label.setAlignment( label.setAlignment(
QtCore.Qt.AlignVCenter align_flag.AlignVCenter
| QtCore.Qt.AlignLeft | align_flag.AlignLeft
) )
self.bar_hbox.addWidget(label) self.bar_hbox.addWidget(label)
@ -617,9 +616,17 @@ class SearchWidget(QtWidgets.QWidget):
self.vbox.addLayout(self.bar_hbox) 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.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: def focus(self) -> None:
self.show() self.show()

View File

@ -22,10 +22,14 @@ from typing import Dict
import math import math
import pyqtgraph as pg import pyqtgraph as pg
from PyQt5 import QtCore, QtGui
from PyQt5.QtCore import Qt, QCoreApplication
from qdarkstyle import DarkPalette from qdarkstyle import DarkPalette
from .qt import (
QtCore,
QtGui,
Qt,
QCoreApplication,
)
from ..log import get_logger from ..log import get_logger
from .. import config from .. import config

View File

@ -27,16 +27,14 @@ from typing import (
) )
import uuid import uuid
from PyQt5 import QtCore from piker.ui.qt import (
from PyQt5.QtWidgets import ( Qt,
QtCore,
QWidget, QWidget,
QMainWindow, QMainWindow,
QApplication, QApplication,
QLabel, QLabel,
QStatusBar, QStatusBar,
)
from PyQt5.QtGui import (
QScreen, QScreen,
QCloseEvent, QCloseEvent,
) )
@ -197,7 +195,9 @@ class MainWindow(QMainWindow):
""" """
# font-size : {font_size}px; # font-size : {font_size}px;
) )
label.setTextFormat(3) # markdown label.setTextFormat(
Qt.TextFormat.MarkdownText
)
label.setFont(_font_small.font) label.setFont(_font_small.font)
label.setMargin(2) label.setMargin(2)
label.setAlignment( label.setAlignment(

View File

@ -34,7 +34,6 @@ import uuid
from bidict import bidict from bidict import bidict
import tractor import tractor
import trio import trio
from PyQt5.QtCore import Qt
from piker import config from piker import config
from piker.accounting import ( from piker.accounting import (
@ -59,6 +58,7 @@ from piker.data import (
) )
from piker.types import Struct from piker.types import Struct
from piker.log import get_logger from piker.log import get_logger
from piker.ui.qt import Qt
from ._editors import LineEditor, ArrowEditor from ._editors import LineEditor, ArrowEditor
from ._lines import order_line, LevelLine from ._lines import order_line, LevelLine
from ._position import ( from ._position import (
@ -358,7 +358,7 @@ class OrderMode:
send_msg: bool = True, send_msg: bool = True,
order: Order | None = None, order: Order | None = None,
) -> Dialog | None: ) -> Dialog|None:
''' '''
Send execution order to EMS return a level line to Send execution order to EMS return a level line to
represent the order on a chart. represent the order on a chart.
@ -494,7 +494,7 @@ class OrderMode:
uuid: str, uuid: str,
order: Order | None = None, order: Order | None = None,
) -> Dialog: ) -> Dialog | None:
''' '''
Order submitted status event handler. Order submitted status event handler.
@ -515,6 +515,11 @@ class OrderMode:
# if an order msg is provided update the line # if an order msg is provided update the line
# **from** that msg. # **from** that msg.
if order: 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) line.set_level(order.price)
self.on_level_change_update_next_order_info( self.on_level_change_update_next_order_info(
level=order.price, level=order.price,
@ -1013,8 +1018,13 @@ async def process_trade_msg(
) -> tuple[Dialog, Status]: ) -> tuple[Dialog, Status]:
fmsg = pformat(msg) # TODO: obvi once we're parsing to native struct instances we can
log.debug(f'Received order msg:\n{fmsg}') # 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'] name = msg['name']
if name in ( if name in (
@ -1030,7 +1040,7 @@ async def process_trade_msg(
): ):
log.info( log.info(
f'Loading position for `{fqme}`:\n' f'Loading position for `{fqme}`:\n'
f'{fmsg}' f'{fmtmsg}'
) )
tracker = mode.trackers[msg['account']] tracker = mode.trackers[msg['account']]
tracker.live_pp.update_from_msg(msg) tracker.live_pp.update_from_msg(msg)
@ -1072,7 +1082,7 @@ async def process_trade_msg(
elif order.action != 'cancel': elif order.action != 'cancel':
log.warning( 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}' assert msg.resp in ('open', 'dark_open'), f'Unknown msg: {msg}'
@ -1139,7 +1149,7 @@ async def process_trade_msg(
req={'exec_mode': 'dark'}, req={'exec_mode': 'dark'},
): ):
# TODO: UX for a "pending" clear/live order # 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( case Status(
resp='triggered', resp='triggered',