pyqt6 #3

Open
goodboy wants to merge 3 commits from pyqt6 into distribute_dis
28 changed files with 486 additions and 259 deletions

View File

@ -14,9 +14,8 @@
# 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/>.
"""
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 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

View File

@ -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(

View File

@ -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

View File

@ -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__(

View File

@ -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.

View File

@ -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(

View File

@ -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}'
)

View File

@ -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,
)

View File

@ -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:
# 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
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

View File

@ -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(

View File

@ -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:

View File

@ -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)

View File

@ -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}')

View File

@ -15,15 +15,18 @@
# 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 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)

View File

@ -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,
}

View File

@ -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

View File

@ -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

View File

@ -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()`,

View File

@ -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,

View File

@ -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

View File

@ -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,
)

View File

@ -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()

View File

@ -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

View File

@ -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(

View File

@ -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',

104
piker/ui/qt.py 100644
View File

@ -0,0 +1,104 @@
# piker: trading gear for hackers
# Copyright (C) Tyler Goodlet (in stewardship for pikers)
# 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 UI framework version shimming.
Allow importing sub-pkgs from this module instead of worrying about
major version specifics, any enum moves or component renames.
Code in `piker.ui.*` should always explicitlyimport directly from
this module like `from piker.ui.qt import ( ..`
'''
from enum import EnumType
from PyQt6 import (
QtCore,
QtGui,
QtWidgets,
)
from PyQt6.QtCore import (
Qt,
QCoreApplication,
QLineF,
QRectF,
# NOTE: for enums use the `.Type` subattr-space
QEvent,
QPointF,
QSize,
QModelIndex,
QItemSelectionModel,
pyqtBoundSignal,
pyqtRemoveInputHook,
)
align_flag: EnumType = Qt.AlignmentFlag
txt_flag: EnumType = Qt.TextFlag
keys: EnumType = QEvent.Type
scrollbar_policy: EnumType = Qt.ScrollBarPolicy
# ^-NOTE-^: handy snippet to discover enums:
# import enum
# [attr for attr_name in dir(QFrame)
# if (attr := getattr(QFrame, attr_name))
# and isinstance(attr, enum.EnumType)]
from PyQt6.QtGui import (
QPainter,
QPainterPath,
QIcon,
QPixmap,
QColor,
QTransform,
QStandardItem,
QStandardItemModel,
QWheelEvent,
QScreen,
QCloseEvent,
)
from PyQt6.QtWidgets import (
QMainWindow,
QApplication,
QLabel,
QStatusBar,
QLineEdit,
QHBoxLayout,
QVBoxLayout,
QFormLayout,
QProgressBar,
QSizePolicy,
QStyledItemDelegate,
QStyleOptionViewItem,
QComboBox,
QWidget,
QFrame,
QSplitter,
QTreeView,
QStyle,
QGraphicsItem,
QGraphicsPathItem,
# QGraphicsView,
QStyleOptionGraphicsItem,
QGraphicsScene,
QGraphicsSceneMouseEvent,
QGraphicsProxyWidget,
)
gs_keys: EnumType = QGraphicsSceneMouseEvent.Type
size_policy: EnumType = QtWidgets.QSizePolicy.Policy
px_cache_mode: EnumType = QGraphicsItem.CacheMode

View File

@ -20,40 +20,31 @@ build-backend = "poetry.core.masonry.api"
# ------ - ------
[tool.ruff.lint]
# https://docs.astral.sh/ruff/settings/#lint_ignore
ignore = []
# https://docs.astral.sh/ruff/settings/#lint_per-file-ignores
"piker/ui/qt.py" = [
"E402",
'F401', # unused imports (without __all__ or blah as blah)
# "F841", # unused variable rules
]
# ignore-init-module-imports = false
# ------ - ------
[tool.poetry]
name = "piker"
version = "0.1.0.alpha0.dev0"
description = "trading gear for hackers"
authors = ["Tyler Goodlet <jgbt@protonmail.com>"]
authors = ["Tyler Goodlet <goodboy_foss@protonmail.com>"]
license = "AGPLv3"
readme = "README.rst"
# TODO: add meta-data from setup.py
# keywords=[
# "async",
# "trading",
# "finance",
# "quant",
# "charting",
# ],
# classifiers=[
# 'Development Status :: 3 - Alpha',
# 'License :: OSI Approved :: ',
# 'Operating System :: POSIX :: Linux',
# "Programming Language :: Python :: Implementation :: CPython",
# "Programming Language :: Python :: 3 :: Only",
# "Programming Language :: Python :: 3.10",
# "Programming Language :: Python :: 3.11",
# 'Intended Audience :: Financial and Insurance Industry',
# 'Intended Audience :: Science/Research',
# 'Intended Audience :: Developers',
# 'Intended Audience :: Education',
# ],
# ------ - ------
[tool.poetry.dependencies]
asks = "^3.0.0"
async-generator = "^1.10"
attrs = "^23.1.0"
bidict = "^0.22.1"
@ -63,41 +54,40 @@ cython = "^3.0.0"
greenback = "^1.1.1"
ib-insync = "^0.9.86"
msgspec = "^0.18.0"
numba = "^0.57.1"
numpy = "1.24"
pendulum = "^2.1.2"
numba = "^0.59.0"
numpy = "^1.25"
polars = "^0.18.13"
pygments = "^2.16.1"
python = "^3.10"
python = ">=3.11, <3.13"
rich = "^13.5.2"
# setuptools = "^68.0.0"
tomli = "^2.0.1"
tomli-w = "^1.0.0"
trio = "^0.22.2"
trio-util = "^0.7.0"
trio-websocket = "^0.10.3"
typer = "^0.9.0"
rapidfuzz = "^3.5.2"
pdbp = "^1.5.0"
trio = "^0.24"
pendulum = "^3.0.0"
httpx = "^0.27.0"
[tool.poetry.dependencies.tractor]
develop = true
git = 'https://github.com/goodboy/tractor.git'
branch = 'asyncio_debugger_support'
# path = "../tractor"
[tool.poetry.dependencies.asyncvnc]
git = 'https://github.com/pikers/asyncvnc.git'
branch = 'main'
[tool.poetry.dependencies.tomlkit]
develop = true
git = 'https://github.com/pikers/tomlkit.git'
branch = 'piker_pin'
develop = true
# path = "../tomlkit/"
[tool.poetry.dependencies.tractor]
git = 'https://github.com/goodboy/tractor.git'
branch = 'asyncio_debugger_support'
# branch = 'piker_pin'
develop = true
# path = '../tractor/'
# ------ - ------
[tool.poetry.group.uis]
optional = true
[tool.poetry.group.uis.dependencies]
@ -106,11 +96,10 @@ optional = true
# rapidfuzz = {extras = ["speedup"], version = "^0.18.0"}
rapidfuzz = "^3.2.0"
qdarkstyle = ">=3.0.2"
pyqt5 = "^5.15.9"
pyqtgraph = { git = 'https://github.com/pikers/pyqtgraph.git' }
pyqt6 = "^6.5.2"
# ------ - ------
pyqt6 = "^6.7.0"
[tool.poetry.group.dev]
optional = true
@ -118,6 +107,8 @@ optional = true
# testing / CI
pytest = "^6.0.0"
elasticsearch = "^8.9.0"
xonsh = "^0.14.2"
prompt-toolkit = "3.0.40"
# console ehancements and eventually remote debugging
# extras/helpers.
@ -126,8 +117,6 @@ elasticsearch = "^8.9.0"
# - xonsh + xxh
# - rsyscall + pdbp
# - actor runtime control console like BEAM/OTP
xonsh = "^0.14.0" # XXX: explicit env install for shell use w nix
prompt-toolkit = "^3.0.39"
# ------ - ------
@ -140,3 +129,26 @@ prompt-toolkit = "^3.0.39"
piker = 'piker.cli:cli'
pikerd = 'piker.cli:pikerd'
ledger = 'piker.accounting.cli:ledger'
[project]
keywords=[
"async",
"trading",
"finance",
"quant",
"charting",
]
classifiers=[
'Development Status :: 3 - Alpha',
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
'Operating System :: POSIX :: Linux',
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
'Intended Audience :: Financial and Insurance Industry',
'Intended Audience :: Science/Research',
'Intended Audience :: Developers',
'Intended Audience :: Education',
]