Skip to content

Commit 538a917

Browse files
committed
Implement rescaling timer in BaseSyncPlot: add methods to handle show and close events, ensuring proper rescaling behavior and preventing unnecessary calls when the widget is closing.
1 parent 223e1f0 commit 538a917

File tree

2 files changed

+43
-3
lines changed

2 files changed

+43
-3
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@
7171

7272
🛠️ Bug fixes:
7373

74+
* Fixed `RuntimeError` in `SyncPlotWindow` and `SyncPlotDialog` when closing widgets quickly:
75+
* Fixed "wrapped C/C++ object of type QwtScaleWidget has been deleted" error that occurred when widgets were closed before the deferred plot rescaling operation could complete
76+
* Replaced `QTimer.singleShot()` with controllable `QTimer` instances that can be stopped on widget close
77+
* Added `handle_show_event()` and `handle_close_event()` methods to `BaseSyncPlot` for proper timer lifecycle management
78+
* Refactored `showEvent()` and `closeEvent()` in both `SyncPlotWindow` and `SyncPlotDialog` to eliminate code duplication
79+
* Added early exit check in `rescale_plots()` to prevent execution if the timer has been stopped
80+
* This fix ensures clean widget shutdown and prevents Qt from attempting to access deleted C++ objects
7481
* Cross-section panels: Fixed autoscaling logic in `BaseCrossSectionPlot`
7582
* Streamlined handling of `autoscale_mode` and `lockscales` options for consistent scaling behavior across all code paths
7683
* The `update_plot()` method now delegates all scaling logic to `plot_axis_changed()` to avoid code duplication and ensure consistency

plotpy/plot/plotwidget.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,7 @@ def __init__(
910910
self.toolbar.setMovable(True)
911911
self.toolbar.setFloatable(True)
912912
self.auto_tools = auto_tools
913+
self._rescale_timer: QC.QTimer | None = None
913914
set_widget_title_icon(self, title, icon, size)
914915
# Note: setup_layout() is called by subclasses after Qt widget initialization
915916

@@ -940,11 +941,33 @@ def finalize_configuration(self) -> None:
940941
self.subplotwidget.register_tools()
941942

942943
def rescale_plots(self) -> None:
943-
"""Rescale all plots"""
944+
"""Rescale all plots
945+
946+
Note: This method is called via QTimer.singleShot, so it may be invoked
947+
even after the widget starts closing. We check if rescaling is still needed.
948+
"""
949+
# Don't rescale if timer was already stopped (widget is closing)
950+
if self._rescale_timer is None or not self._rescale_timer.isActive():
951+
return
952+
944953
QW.QApplication.instance().processEvents()
945954
for plot in self.subplotwidget.plots:
946955
plot.do_autoscale()
947956

957+
def handle_show_event(self) -> None:
958+
"""Handle the show event to trigger plot rescaling"""
959+
# Use a QTimer instance so we can stop it if the widget is closed quickly
960+
if self._rescale_timer is None:
961+
self._rescale_timer = QC.QTimer(self)
962+
self._rescale_timer.setSingleShot(True)
963+
self._rescale_timer.timeout.connect(self.rescale_plots)
964+
self._rescale_timer.start(0)
965+
966+
def handle_close_event(self) -> None:
967+
"""Handle the close event to stop pending timer"""
968+
if self._rescale_timer is not None and self._rescale_timer.isActive():
969+
self._rescale_timer.stop()
970+
948971
def add_plot(
949972
self,
950973
row: int,
@@ -1032,7 +1055,12 @@ def __init__(
10321055
def showEvent(self, event): # pylint: disable=C0103
10331056
"""Reimplement Qt method"""
10341057
super().showEvent(event)
1035-
QC.QTimer.singleShot(0, self.rescale_plots)
1058+
self.handle_show_event()
1059+
1060+
def closeEvent(self, event): # pylint: disable=C0103
1061+
"""Reimplement Qt method to stop pending timer before closing"""
1062+
self.handle_close_event()
1063+
super().closeEvent(event)
10361064

10371065
def setup_layout(self) -> None:
10381066
"""Setup the main window layout"""
@@ -1087,7 +1115,12 @@ def __init__(
10871115
def showEvent(self, event): # pylint: disable=C0103
10881116
"""Reimplement Qt method"""
10891117
super().showEvent(event)
1090-
QC.QTimer.singleShot(0, self.rescale_plots)
1118+
self.handle_show_event()
1119+
1120+
def closeEvent(self, event): # pylint: disable=C0103
1121+
"""Reimplement Qt method to stop pending timer before closing"""
1122+
self.handle_close_event()
1123+
super().closeEvent(event)
10911124

10921125
def setup_layout(self) -> None:
10931126
"""Setup the dialog layout"""

0 commit comments

Comments
 (0)