mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-07-30 07:03:01 +02:00
build: support PySide6 6.11.1, pyqtgraph 0.14 and PySide6-QtAds 5.0.0, remove force x11 on Linux
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
@@ -68,6 +68,10 @@ def qapplication(qtbot, request, testable_qtimer_class): # pylint: disable=unus
|
||||
# stop pyepics dispatcher for leaking tests
|
||||
|
||||
_dispatcher.stop()
|
||||
from bec_widgets.widgets.editors.bec_console.bec_console import _bec_console_registry
|
||||
|
||||
_bec_console_registry.clear()
|
||||
process_all_deferred_deletes(qapp)
|
||||
if request.node.stash._storage.get("failed"):
|
||||
print("Test failed, skipping cleanup checks")
|
||||
return
|
||||
|
||||
@@ -14,6 +14,33 @@ from .conftest import create_widget
|
||||
# pylint: disable = redefined-outer-name
|
||||
|
||||
|
||||
class _FakeClickEvent:
|
||||
"""Minimal public-API stand-in for a pyqtgraph MouseClickEvent."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
scene_pos: QPointF,
|
||||
button: Qt.MouseButton = Qt.MouseButton.LeftButton,
|
||||
double: bool = False,
|
||||
):
|
||||
self._scene_pos = scene_pos
|
||||
self._button = button
|
||||
self._double = double
|
||||
self.accepted = False
|
||||
|
||||
def button(self):
|
||||
return self._button
|
||||
|
||||
def scenePos(self):
|
||||
return self._scene_pos
|
||||
|
||||
def double(self):
|
||||
return self._double
|
||||
|
||||
def accept(self):
|
||||
self.accepted = True
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def plot_widget_with_crosshair(qtbot):
|
||||
widget = pg.PlotWidget()
|
||||
@@ -22,6 +49,7 @@ def plot_widget_with_crosshair(qtbot):
|
||||
|
||||
widget.plot(x=[1, 2, 3], y=[4, 5, 6], name="Curve 1")
|
||||
plot_item = widget.getPlotItem()
|
||||
plot_item.vb.setRange(xRange=(0, 4), yRange=(0, 10), padding=0)
|
||||
crosshair = Crosshair(plot_item=plot_item, precision=3)
|
||||
|
||||
yield crosshair, plot_item
|
||||
@@ -38,20 +66,17 @@ def image_widget_with_crosshair(qtbot):
|
||||
|
||||
widget.addItem(image_item)
|
||||
plot_item = widget.getPlotItem()
|
||||
plot_item.vb.setRange(xRange=(0, 100), yRange=(0, 100), padding=0)
|
||||
crosshair = Crosshair(plot_item=plot_item, precision=3)
|
||||
|
||||
yield crosshair, plot_item
|
||||
|
||||
|
||||
def test_mouse_moved_lines(plot_widget_with_crosshair):
|
||||
crosshair, plot_item = plot_widget_with_crosshair
|
||||
|
||||
pos_in_view = QPointF(2, 5)
|
||||
pos_in_scene = plot_item.vb.mapViewToScene(pos_in_view)
|
||||
event_mock = [pos_in_scene]
|
||||
crosshair, _ = plot_widget_with_crosshair
|
||||
|
||||
# Simulate mouse movement
|
||||
crosshair.mouse_moved(event_mock)
|
||||
crosshair.mouse_moved(manual_pos=(2, 5))
|
||||
|
||||
# Check that the vertical line is indeed at x=2
|
||||
assert np.isclose(crosshair.v_line.pos().x(), 2)
|
||||
@@ -59,7 +84,7 @@ def test_mouse_moved_lines(plot_widget_with_crosshair):
|
||||
|
||||
|
||||
def test_mouse_moved_signals(plot_widget_with_crosshair):
|
||||
crosshair, plot_item = plot_widget_with_crosshair
|
||||
crosshair, _ = plot_widget_with_crosshair
|
||||
|
||||
emitted_values_1D = []
|
||||
|
||||
@@ -68,43 +93,40 @@ def test_mouse_moved_signals(plot_widget_with_crosshair):
|
||||
|
||||
crosshair.coordinatesChanged1D.connect(slot)
|
||||
|
||||
pos_in_view = QPointF(2, 5)
|
||||
pos_in_scene = plot_item.vb.mapViewToScene(pos_in_view)
|
||||
event_mock = [pos_in_scene]
|
||||
|
||||
crosshair.mouse_moved(event_mock)
|
||||
crosshair.mouse_moved(manual_pos=(2, 5))
|
||||
|
||||
# Assert the expected behavior
|
||||
assert emitted_values_1D == [("Curve 1", 2, 5)]
|
||||
|
||||
|
||||
def test_mouse_moved_signals_outside(plot_widget_with_crosshair):
|
||||
crosshair, plot_item = plot_widget_with_crosshair
|
||||
crosshair, _ = plot_widget_with_crosshair
|
||||
|
||||
# Create a slot that will store the emitted values as tuples
|
||||
emitted_values_1D = []
|
||||
emitted_positions = []
|
||||
|
||||
def slot(coordinates):
|
||||
emitted_values_1D.append(coordinates)
|
||||
|
||||
# Connect the signal to the custom slot
|
||||
crosshair.coordinatesChanged1D.connect(slot)
|
||||
crosshair.crosshairChanged.connect(emitted_positions.append)
|
||||
|
||||
# Simulate a mouse moved event at a specific position
|
||||
pos_in_view = QPointF(22, 55)
|
||||
pos_in_scene = plot_item.vb.mapViewToScene(pos_in_view)
|
||||
event_mock = [pos_in_scene]
|
||||
|
||||
# Call the mouse_moved method
|
||||
crosshair.mouse_moved(event_mock)
|
||||
crosshair.mouse_moved(manual_pos=(2, 5))
|
||||
emitted_positions.clear()
|
||||
emitted_values_1D.clear()
|
||||
crosshair.mouse_moved(manual_pos=(22, 55))
|
||||
|
||||
# Assert the expected behavior
|
||||
assert emitted_values_1D == []
|
||||
assert emitted_positions == []
|
||||
assert np.isclose(crosshair.v_line.pos().x(), 2)
|
||||
assert np.isclose(crosshair.h_line.pos().y(), 5)
|
||||
|
||||
|
||||
def test_mouse_moved_signals_2D(image_widget_with_crosshair):
|
||||
crosshair, plot_item = image_widget_with_crosshair
|
||||
image_item = plot_item.items[0]
|
||||
crosshair, _ = image_widget_with_crosshair
|
||||
|
||||
emitted_values_2D = []
|
||||
|
||||
@@ -113,17 +135,16 @@ def test_mouse_moved_signals_2D(image_widget_with_crosshair):
|
||||
|
||||
crosshair.coordinatesChanged2D.connect(slot)
|
||||
|
||||
pos_in_view = QPointF(21.0, 55.0)
|
||||
pos_in_scene = plot_item.vb.mapViewToScene(pos_in_view)
|
||||
event_mock = [pos_in_scene]
|
||||
|
||||
crosshair.mouse_moved(event_mock)
|
||||
crosshair.mouse_moved(manual_pos=(21.0, 55.0))
|
||||
|
||||
assert emitted_values_2D == [("ImageItem", 21, 55)]
|
||||
|
||||
|
||||
def test_mouse_moved_signals_2D_outside(image_widget_with_crosshair):
|
||||
def test_mouse_moved_signals_2D_outside_image_bounds_clamps_inside_view_range(
|
||||
image_widget_with_crosshair,
|
||||
):
|
||||
crosshair, plot_item = image_widget_with_crosshair
|
||||
plot_item.vb.setRange(xRange=(0, 300), yRange=(0, 600), padding=0)
|
||||
|
||||
emitted_values_2D = []
|
||||
|
||||
@@ -132,23 +153,34 @@ def test_mouse_moved_signals_2D_outside(image_widget_with_crosshair):
|
||||
|
||||
crosshair.coordinatesChanged2D.connect(slot)
|
||||
|
||||
pos_in_view = QPointF(220.0, 555.0)
|
||||
pos_in_scene = plot_item.vb.mapViewToScene(pos_in_view)
|
||||
event_mock = [pos_in_scene]
|
||||
crosshair.mouse_moved(manual_pos=(220.0, 555.0))
|
||||
|
||||
crosshair.mouse_moved(event_mock)
|
||||
assert emitted_values_2D == [("ImageItem", 99, 99)]
|
||||
|
||||
|
||||
def test_mouse_moved_signals_2D_outside_view_range_ignored(image_widget_with_crosshair):
|
||||
crosshair, _ = image_widget_with_crosshair
|
||||
|
||||
emitted_values_2D = []
|
||||
emitted_positions = []
|
||||
|
||||
crosshair.coordinatesChanged2D.connect(emitted_values_2D.append)
|
||||
crosshair.crosshairChanged.connect(emitted_positions.append)
|
||||
|
||||
crosshair.mouse_moved(manual_pos=(21.0, 55.0))
|
||||
emitted_positions.clear()
|
||||
emitted_values_2D.clear()
|
||||
crosshair.mouse_moved(manual_pos=(220.0, 555.0))
|
||||
|
||||
assert emitted_values_2D == []
|
||||
assert emitted_positions == []
|
||||
assert np.isclose(crosshair.v_line.pos().x(), 21.0)
|
||||
assert np.isclose(crosshair.h_line.pos().y(), 55.0)
|
||||
|
||||
|
||||
def test_marker_positions_after_mouse_move(plot_widget_with_crosshair):
|
||||
crosshair, plot_item = plot_widget_with_crosshair
|
||||
|
||||
pos_in_view = QPointF(2, 5)
|
||||
pos_in_scene = plot_item.vb.mapViewToScene(pos_in_view)
|
||||
event_mock = [pos_in_scene]
|
||||
|
||||
crosshair.mouse_moved(event_mock)
|
||||
crosshair, _ = plot_widget_with_crosshair
|
||||
crosshair.mouse_moved(manual_pos=(2, 5))
|
||||
|
||||
marker = crosshair.marker_moved_1d["Curve 1"]
|
||||
marker_x, marker_y = marker.getData()
|
||||
@@ -172,7 +204,7 @@ def test_scale_emitted_coordinates(plot_widget_with_crosshair):
|
||||
|
||||
|
||||
def test_crosshair_changed_signal(plot_widget_with_crosshair):
|
||||
crosshair, plot_item = plot_widget_with_crosshair
|
||||
crosshair, _ = plot_widget_with_crosshair
|
||||
|
||||
emitted_positions = []
|
||||
|
||||
@@ -181,11 +213,7 @@ def test_crosshair_changed_signal(plot_widget_with_crosshair):
|
||||
|
||||
crosshair.crosshairChanged.connect(slot)
|
||||
|
||||
pos_in_view = QPointF(2, 5)
|
||||
pos_in_scene = plot_item.vb.mapViewToScene(pos_in_view)
|
||||
event_mock = [pos_in_scene]
|
||||
|
||||
crosshair.mouse_moved(event_mock)
|
||||
crosshair.mouse_moved(manual_pos=(2, 5))
|
||||
|
||||
x, y = emitted_positions[0]
|
||||
|
||||
@@ -193,33 +221,33 @@ def test_crosshair_changed_signal(plot_widget_with_crosshair):
|
||||
assert np.isclose(y, 5)
|
||||
|
||||
|
||||
def test_crosshair_clicked_signal(qtbot, plot_widget_with_crosshair):
|
||||
def test_crosshair_clicked_signal(plot_widget_with_crosshair):
|
||||
crosshair, plot_item = plot_widget_with_crosshair
|
||||
|
||||
emitted_positions = []
|
||||
emitted_view_positions = []
|
||||
|
||||
def slot(position):
|
||||
emitted_positions.append(position)
|
||||
|
||||
crosshair.crosshairClicked.connect(slot)
|
||||
crosshair.positionClicked.connect(emitted_view_positions.append)
|
||||
|
||||
x_data = 2
|
||||
y_data = 5
|
||||
crosshair.is_log_x = True
|
||||
crosshair.is_log_y = True
|
||||
plot_item.vb.setRange(xRange=(0, 1), yRange=(0, 1), padding=0)
|
||||
|
||||
# Map data coordinates to scene coordinates
|
||||
pos_in_scene = plot_item.vb.mapViewToScene(QPointF(x_data, y_data))
|
||||
# Map scene coordinates to widget coordinates
|
||||
graphics_view = plot_item.vb.scene().views()[0]
|
||||
qtbot.waitExposed(graphics_view)
|
||||
pos_in_widget = graphics_view.mapFromScene(pos_in_scene)
|
||||
|
||||
# Simulate mouse click
|
||||
qtbot.mouseClick(graphics_view.viewport(), Qt.LeftButton, pos=pos_in_widget)
|
||||
known_view_point = QPointF(np.log10(2), np.log10(5))
|
||||
pos_in_scene = plot_item.vb.mapViewToScene(known_view_point)
|
||||
crosshair.mouse_clicked(_FakeClickEvent(pos_in_scene))
|
||||
|
||||
x, y = emitted_positions[0]
|
||||
view_x, view_y = emitted_view_positions[0]
|
||||
|
||||
assert np.isclose(round(x, 1), 2)
|
||||
assert np.isclose(round(y, 1), 5)
|
||||
assert np.isclose(x, 2)
|
||||
assert np.isclose(y, 5)
|
||||
assert np.isclose(view_x, known_view_point.x())
|
||||
assert np.isclose(view_y, known_view_point.y())
|
||||
|
||||
|
||||
def test_update_coord_label_1D(plot_widget_with_crosshair):
|
||||
@@ -388,21 +416,18 @@ def test_ignore_invisible_curves_on_move(qtbot, mocked_client):
|
||||
c0 = wf.plot(x=[1, 2, 3], y=[1, 4, 9], name="Curve_0")
|
||||
c1 = wf.plot(x=[1, 2, 3], y=[2, 5, 10], name="Curve_1")
|
||||
wf.hook_crosshair()
|
||||
wf.crosshair.plot_item.vb.setRange(xRange=(0, 4), yRange=(0, 10), padding=0)
|
||||
|
||||
# # Simulate a mouse move at (2,5)
|
||||
pos_in_view = QPointF(2, 5)
|
||||
pos_in_scene = wf.plot_item.vb.mapViewToScene(pos_in_view)
|
||||
event_mock = [pos_in_scene]
|
||||
|
||||
# 1) Both curves visible: expect markers for both
|
||||
wf.crosshair.clear_markers()
|
||||
wf.crosshair.mouse_moved(event_mock)
|
||||
wf.crosshair.mouse_moved(manual_pos=(2, 5))
|
||||
assert set(wf.crosshair.marker_moved_1d.keys()) == {"Curve_0", "Curve_1"}
|
||||
|
||||
# 2) Hide Curve B and repeat: only Curve_0 should remain
|
||||
c1.setVisible(False)
|
||||
wf.crosshair.clear_markers()
|
||||
wf.crosshair.mouse_moved(event_mock)
|
||||
wf.crosshair.mouse_moved(manual_pos=(2, 5))
|
||||
qtbot.wait(200)
|
||||
assert set(wf.crosshair.marker_moved_1d.keys()) == {"Curve_0"}
|
||||
|
||||
@@ -412,25 +437,6 @@ def test_ignore_invisible_curves_on_move(qtbot, mocked_client):
|
||||
###############################################
|
||||
|
||||
|
||||
class _FakeClickEvent:
|
||||
"""Minimal stand-in for a pyqtgraph MouseClickEvent for routing tests."""
|
||||
|
||||
def __init__(self, scene_pos, button=Qt.LeftButton, double=False):
|
||||
self._scenePos = scene_pos
|
||||
self._button = button
|
||||
self._double = double
|
||||
self.accepted = False
|
||||
|
||||
def button(self):
|
||||
return self._button
|
||||
|
||||
def double(self):
|
||||
return self._double
|
||||
|
||||
def accept(self):
|
||||
self.accepted = True
|
||||
|
||||
|
||||
def test_set_pin_1d_creates_marker_and_emits(plot_widget_with_crosshair):
|
||||
crosshair, _ = plot_widget_with_crosshair
|
||||
pinned = []
|
||||
|
||||
@@ -531,6 +531,7 @@ class TestDeviceManagerView:
|
||||
# Assign the mocked client
|
||||
widget.device_manager_widget.client = mocked_client
|
||||
qtbot.addWidget(widget)
|
||||
widget.show()
|
||||
qtbot.waitExposed(widget)
|
||||
yield widget
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ from bec_widgets.widgets.plots.heatmap.heatmap import (
|
||||
HeatmapDeviceSignal,
|
||||
_InterpolationRequest,
|
||||
_StepInterpolationWorker,
|
||||
_TextOnlyLegendSample,
|
||||
)
|
||||
|
||||
# pytest: disable=unused-import
|
||||
@@ -1055,6 +1056,27 @@ def test_heatmap_config_label_shows_live_or_history(heatmap_widget):
|
||||
assert "Scan: 5 (history)" in labels
|
||||
|
||||
|
||||
def test_heatmap_config_label_paints_without_error(heatmap_widget):
|
||||
"""The config label rows must survive a real paint pass.
|
||||
|
||||
A stock ItemSample around a PlotItem raises in paint(); since PySide 6.10
|
||||
override exceptions propagate out of the C++ paint loop and segfault.
|
||||
"""
|
||||
scan_msg = mock.MagicMock()
|
||||
scan_msg.scan_number = 5
|
||||
scan_msg.scan_name = "line_scan"
|
||||
heatmap_widget.status_message = scan_msg
|
||||
heatmap_widget._image_config.show_config_label = True
|
||||
heatmap_widget.redraw_config_label()
|
||||
|
||||
samples = [sample for sample, _ in heatmap_widget.config_label.items]
|
||||
assert samples
|
||||
assert all(isinstance(sample, _TextOnlyLegendSample) for sample in samples)
|
||||
|
||||
pixmap = heatmap_widget.grab()
|
||||
assert not pixmap.isNull()
|
||||
|
||||
|
||||
def test_heatmap_settings_scan_index_syncs_with_widget(heatmap_widget, qtbot, scan_history_factory):
|
||||
"""
|
||||
The scan index combobox in the settings dialog mirrors the heatmap state:
|
||||
|
||||
@@ -1391,6 +1391,9 @@ def test_crosshair_moves_update_profiles_immediately(qtbot, mocked_client):
|
||||
switch = bec_image_view.toolbar.components.get_action("image_switch_crosshair")
|
||||
switch.actions["crosshair_roi"].action.trigger()
|
||||
qtbot.wait(50)
|
||||
bec_image_view.crosshair.plot_item.vb.setRange(
|
||||
xRange=(0, test_data.shape[0]), yRange=(0, test_data.shape[1]), padding=0
|
||||
)
|
||||
|
||||
# Position the crosshair lines and emit the snapped pixel (as mouse_moved does).
|
||||
bec_image_view.crosshair.mouse_moved(manual_pos=(1.2, 2.2))
|
||||
@@ -1410,6 +1413,9 @@ def test_live_label_intensity_updates_on_image_update(qtbot, mocked_client):
|
||||
test_data = np.arange(25, dtype=float).reshape(5, 5)
|
||||
bec_image_view.on_image_update_2d({"data": test_data}, {})
|
||||
bec_image_view.hook_crosshair()
|
||||
bec_image_view.crosshair.plot_item.vb.setRange(
|
||||
xRange=(0, test_data.shape[0]), yRange=(0, test_data.shape[1]), padding=0
|
||||
)
|
||||
|
||||
bec_image_view.crosshair.mouse_moved(manual_pos=(2.5, 3.5))
|
||||
assert "Intensity: 13.000" in bec_image_view.crosshair.coord_label.toPlainText()
|
||||
|
||||
@@ -80,7 +80,7 @@ def test_export_import_config(example_widget):
|
||||
"value": 10,
|
||||
},
|
||||
"QTableWidget ()": {
|
||||
"QAbstractButton ()": {},
|
||||
"QAbstractButton (qt_tableview_cornerbutton)": {},
|
||||
"QAbstractTableModel ()": {},
|
||||
"QHeaderView ()": {
|
||||
"QItemSelectionModel ()": {},
|
||||
|
||||
Reference in New Issue
Block a user