Fix Qt indexing in `GapAnnotations.reposition()`
`prepareGeometryChange()` ran after the gap rects and arrow specs were mutated. Qt could retain the item's old scene index even though `boundingRect()` returned its new prepended-history position. Invalidate cached bounds and notify Qt before changing any geometry. Cover both scene lookup positions with a real offscreen item. (this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))chart_local_gapper
parent
78982d8280
commit
711666f9c8
|
|
@ -670,6 +670,11 @@ class GapAnnotations(GraphicsObject):
|
|||
)
|
||||
}
|
||||
|
||||
# Notify Qt before mutating anything used by
|
||||
# `boundingRect()`.
|
||||
self.prepareGeometryChange()
|
||||
self._br = None
|
||||
|
||||
# rebuild rect array from gap specs with new indices
|
||||
rect_memory = self._rectarray.ndarray()
|
||||
|
||||
|
|
@ -777,7 +782,4 @@ class GapAnnotations(GraphicsObject):
|
|||
self._arrow_path.addPolygon(arrow_poly)
|
||||
self._arrow_path.closeSubpath()
|
||||
|
||||
# invalidate bounding rect cache
|
||||
self._br = None
|
||||
self.prepareGeometryChange()
|
||||
self.update()
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ os.environ['QT_QPA_PLATFORM'] = 'offscreen'
|
|||
|
||||
import msgspec
|
||||
import numpy as np
|
||||
from PyQt6.QtWidgets import QGraphicsScene
|
||||
import pyqtgraph as pg
|
||||
import pytest
|
||||
|
||||
|
|
@ -22,7 +23,11 @@ from piker.ui._gaps import (
|
|||
SetGapOverlay,
|
||||
gap_specs_from_ohlcv,
|
||||
)
|
||||
from piker.ui.qt import QApplication
|
||||
from piker.ui.qt import (
|
||||
QApplication,
|
||||
QPointF,
|
||||
QRectF,
|
||||
)
|
||||
|
||||
|
||||
FQME: str = 'gap.test'
|
||||
|
|
@ -387,6 +392,79 @@ def test_gap_manager_real_qt_lifecycle(
|
|||
qapp.processEvents()
|
||||
|
||||
|
||||
def test_gap_annotations_reposition_after_prepend(
|
||||
qapp: QApplication,
|
||||
|
||||
) -> None:
|
||||
'''
|
||||
Reposition live gap geometry after history index offsets change.
|
||||
|
||||
A history prepend preserves timestamps but shifts every absolute
|
||||
chart index. Gap rectangles and arrows otherwise remain attached
|
||||
to their pre-prepend x coordinates. Render one real gap item,
|
||||
shift its source indexes by ten, call `reposition()`, and prove
|
||||
its rectangle bounds and arrow spec move together while the item
|
||||
stays attached to its real Qt scene.
|
||||
|
||||
'''
|
||||
ds: SimpleNamespace
|
||||
charts: tuple[_ChartStub, _ChartStub]
|
||||
ds, charts = _display_state(FQME)
|
||||
annots: dict[int, GapAnnotations] = {}
|
||||
gapman: GapOverlayMngr = GapOverlayMngr(
|
||||
dss={FQME: ds},
|
||||
annots=annots,
|
||||
)
|
||||
|
||||
try:
|
||||
gap: GapOverlay = gapman.refresh(
|
||||
ds=ds,
|
||||
timeframe=60,
|
||||
)
|
||||
assert gap.aid is not None
|
||||
item: GapAnnotations = annots[gap.aid]
|
||||
old_br: QRectF = item.boundingRect()
|
||||
scene: QGraphicsScene = charts[1].widget.scene()
|
||||
old_scene_point: QPointF = item.mapToScene(
|
||||
old_br.center()
|
||||
)
|
||||
qapp.processEvents()
|
||||
assert item in scene.items(old_scene_point)
|
||||
old_rects: np.ndarray = (
|
||||
item._rectarray.ndarray().copy()
|
||||
)
|
||||
shifted: np.ndarray = ds.hist_viz.shm.array.copy()
|
||||
shifted['index'] += 10
|
||||
|
||||
item.reposition(
|
||||
array=shifted,
|
||||
fqme=FQME,
|
||||
timeframe=60,
|
||||
)
|
||||
qapp.processEvents()
|
||||
|
||||
new_rects: np.ndarray = item._rectarray.ndarray()
|
||||
np.testing.assert_allclose(
|
||||
new_rects[:, 0],
|
||||
old_rects[:, 0] + 10,
|
||||
)
|
||||
assert item._gap_specs[0]['arrow_x'] == 12
|
||||
new_br: QRectF = item.boundingRect()
|
||||
new_scene_point: QPointF = item.mapToScene(
|
||||
new_br.center()
|
||||
)
|
||||
assert new_br.left() == old_br.left() + 10
|
||||
assert item in scene.items(new_scene_point)
|
||||
assert item not in scene.items(old_scene_point)
|
||||
assert item.scene() is charts[1].widget.scene()
|
||||
finally:
|
||||
gapman.remove_owner('chart-local')
|
||||
chart: _ChartStub
|
||||
for chart in charts:
|
||||
chart.close()
|
||||
qapp.processEvents()
|
||||
|
||||
|
||||
def test_duplicate_fqme_layers_use_local_chart_identity(
|
||||
qapp: QApplication,
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue