diff --git a/pyqtgraph/Qt.py b/pyqtgraph/Qt.py index 410bfd83..62ffa2d0 100644 --- a/pyqtgraph/Qt.py +++ b/pyqtgraph/Qt.py @@ -32,6 +32,9 @@ else: if USE_PYSIDE: from PySide import QtGui, QtCore, QtOpenGL, QtSvg import PySide + from PySide import shiboken + isQObjectAlive = shiboken.isValid + VERSION_INFO = 'PySide ' + PySide.__version__ # Make a loadUiType function like PyQt has @@ -78,6 +81,8 @@ else: pass + import sip + isQObjectAlive = sip.isdeleted loadUiType = uic.loadUiType QtCore.Signal = QtCore.pyqtSignal diff --git a/pyqtgraph/__init__.py b/pyqtgraph/__init__.py index 5f42e64f..cd78cfa8 100644 --- a/pyqtgraph/__init__.py +++ b/pyqtgraph/__init__.py @@ -256,6 +256,7 @@ from .graphicsWindows import * from .SignalProxy import * from .colormap import * from .ptime import time +from pyqtgraph.Qt import isQObjectAlive ############################################################## @@ -284,7 +285,10 @@ def cleanup(): s = QtGui.QGraphicsScene() for o in gc.get_objects(): try: - if isinstance(o, QtGui.QGraphicsItem) and o.scene() is None: + if isinstance(o, QtGui.QGraphicsItem) and isQObjectAlive(o) and o.scene() is None: + sys.stderr.write( + 'Error: graphics item without scene. Make sure ViewBox.close() and GraphicsView.close() are properly called before app shutdown (%s)\n' % (o,)) + s.addItem(o) except RuntimeError: ## occurs if a python wrapper no longer has its underlying C++ object continue diff --git a/pyqtgraph/graphicsItems/PlotItem/PlotItem.py b/pyqtgraph/graphicsItems/PlotItem/PlotItem.py index 7c02a534..c0b9adab 100644 --- a/pyqtgraph/graphicsItems/PlotItem/PlotItem.py +++ b/pyqtgraph/graphicsItems/PlotItem/PlotItem.py @@ -18,6 +18,7 @@ This class is very heavily featured: """ from ...Qt import QtGui, QtCore, QtSvg, USE_PYSIDE from ... import pixmaps +import sys if USE_PYSIDE: from .plotConfigTemplate_pyside import * @@ -193,14 +194,6 @@ class PlotItem(GraphicsWidget): self.layout.setColumnStretchFactor(1, 100) - ## Wrap a few methods from viewBox - for m in [ - 'setXRange', 'setYRange', 'setXLink', 'setYLink', 'setAutoPan', 'setAutoVisible', - 'setRange', 'autoRange', 'viewRect', 'viewRange', 'setMouseEnabled', 'setLimits', - 'enableAutoRange', 'disableAutoRange', 'setAspectLocked', 'invertY', - 'register', 'unregister']: ## NOTE: If you update this list, please update the class docstring as well. - setattr(self, m, getattr(self.vb, m)) - self.items = [] self.curves = [] self.itemMeta = weakref.WeakKeyDictionary() @@ -298,7 +291,26 @@ class PlotItem(GraphicsWidget): """Return the :class:`ViewBox ` contained within.""" return self.vb + ## Wrap a few methods from viewBox. + #Important: don't use a settattr(m, getattr(self.vb, m)) as we'd be leaving the viebox alive + #because we had a reference to an instance method (creating wrapper methods at runtime instead). + frame = sys._getframe() + for m in [ + 'setXRange', 'setYRange', 'setXLink', 'setYLink', 'setAutoPan', 'setAutoVisible', + 'setRange', 'autoRange', 'viewRect', 'viewRange', 'setMouseEnabled', 'setLimits', + 'enableAutoRange', 'disableAutoRange', 'setAspectLocked', 'invertY', + 'register', 'unregister']: ## NOTE: If you update this list, please update the class docstring as well. + def _create_method(name): # @NoSelf + def method(self, *args, **kwargs): + return getattr(self.vb, name)(*args, **kwargs) + method.__name__ = name + return method + + frame.f_locals[m] = _create_method(m) + + del _create_method + del frame def setLogMode(self, x=None, y=None): """ @@ -356,10 +368,8 @@ class PlotItem(GraphicsWidget): self.ctrlMenu.setParent(None) self.ctrlMenu = None - #self.ctrlBtn.setParent(None) - #self.ctrlBtn = None - #self.autoBtn.setParent(None) - #self.autoBtn = None + self.autoBtn.setParent(None) + self.autoBtn = None for k in self.axes: i = self.axes[k]['item'] diff --git a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py index 58b2aeba..ef5b4319 100644 --- a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py +++ b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py @@ -5,11 +5,12 @@ from ...Point import Point from ... import functions as fn from .. ItemGroup import ItemGroup from .. GraphicsWidget import GraphicsWidget -from ...GraphicsScene import GraphicsScene import weakref from copy import deepcopy from ... import debug as debug from ... import getConfigOption +import sys +from pyqtgraph.Qt import isQObjectAlive __all__ = ['ViewBox'] @@ -240,6 +241,7 @@ class ViewBox(GraphicsWidget): del ViewBox.NamedViews[self.name] def close(self): + self.clear() self.unregister() def implements(self, interface): @@ -1653,6 +1655,9 @@ class ViewBox(GraphicsWidget): ## called when the application is about to exit. ## this disables all callbacks, which might otherwise generate errors if invoked during exit. for k in ViewBox.AllViews: + if isQObjectAlive(k): + sys.stderr.write('ViewBox should be closed before application exit!') + try: k.destroyed.disconnect() except RuntimeError: ## signal is already disconnected.