Make sure we don't leave view boxes alive by doing a 'bridge' for the on the plot items. Also added warnings if proper cleanup wasn't done.

This commit is contained in:
fabioz 2014-03-13 17:38:50 -03:00
parent 1edf1375ed
commit 6433795e78
4 changed files with 38 additions and 14 deletions

View File

@ -32,6 +32,9 @@ else:
if USE_PYSIDE: if USE_PYSIDE:
from PySide import QtGui, QtCore, QtOpenGL, QtSvg from PySide import QtGui, QtCore, QtOpenGL, QtSvg
import PySide import PySide
from PySide import shiboken
isQObjectAlive = shiboken.isValid
VERSION_INFO = 'PySide ' + PySide.__version__ VERSION_INFO = 'PySide ' + PySide.__version__
# Make a loadUiType function like PyQt has # Make a loadUiType function like PyQt has
@ -78,6 +81,8 @@ else:
pass pass
import sip
isQObjectAlive = sip.isdeleted
loadUiType = uic.loadUiType loadUiType = uic.loadUiType
QtCore.Signal = QtCore.pyqtSignal QtCore.Signal = QtCore.pyqtSignal

View File

@ -256,6 +256,7 @@ from .graphicsWindows import *
from .SignalProxy import * from .SignalProxy import *
from .colormap import * from .colormap import *
from .ptime import time from .ptime import time
from pyqtgraph.Qt import isQObjectAlive
############################################################## ##############################################################
@ -284,7 +285,10 @@ def cleanup():
s = QtGui.QGraphicsScene() s = QtGui.QGraphicsScene()
for o in gc.get_objects(): for o in gc.get_objects():
try: 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) s.addItem(o)
except RuntimeError: ## occurs if a python wrapper no longer has its underlying C++ object except RuntimeError: ## occurs if a python wrapper no longer has its underlying C++ object
continue continue

View File

@ -18,6 +18,7 @@ This class is very heavily featured:
""" """
from ...Qt import QtGui, QtCore, QtSvg, USE_PYSIDE from ...Qt import QtGui, QtCore, QtSvg, USE_PYSIDE
from ... import pixmaps from ... import pixmaps
import sys
if USE_PYSIDE: if USE_PYSIDE:
from .plotConfigTemplate_pyside import * from .plotConfigTemplate_pyside import *
@ -193,14 +194,6 @@ class PlotItem(GraphicsWidget):
self.layout.setColumnStretchFactor(1, 100) 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.items = []
self.curves = [] self.curves = []
self.itemMeta = weakref.WeakKeyDictionary() self.itemMeta = weakref.WeakKeyDictionary()
@ -298,7 +291,26 @@ class PlotItem(GraphicsWidget):
"""Return the :class:`ViewBox <pyqtgraph.ViewBox>` contained within.""" """Return the :class:`ViewBox <pyqtgraph.ViewBox>` contained within."""
return self.vb 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): def setLogMode(self, x=None, y=None):
""" """
@ -356,10 +368,8 @@ class PlotItem(GraphicsWidget):
self.ctrlMenu.setParent(None) self.ctrlMenu.setParent(None)
self.ctrlMenu = None self.ctrlMenu = None
#self.ctrlBtn.setParent(None) self.autoBtn.setParent(None)
#self.ctrlBtn = None self.autoBtn = None
#self.autoBtn.setParent(None)
#self.autoBtn = None
for k in self.axes: for k in self.axes:
i = self.axes[k]['item'] i = self.axes[k]['item']

View File

@ -5,11 +5,12 @@ from ...Point import Point
from ... import functions as fn from ... import functions as fn
from .. ItemGroup import ItemGroup from .. ItemGroup import ItemGroup
from .. GraphicsWidget import GraphicsWidget from .. GraphicsWidget import GraphicsWidget
from ...GraphicsScene import GraphicsScene
import weakref import weakref
from copy import deepcopy from copy import deepcopy
from ... import debug as debug from ... import debug as debug
from ... import getConfigOption from ... import getConfigOption
import sys
from pyqtgraph.Qt import isQObjectAlive
__all__ = ['ViewBox'] __all__ = ['ViewBox']
@ -240,6 +241,7 @@ class ViewBox(GraphicsWidget):
del ViewBox.NamedViews[self.name] del ViewBox.NamedViews[self.name]
def close(self): def close(self):
self.clear()
self.unregister() self.unregister()
def implements(self, interface): def implements(self, interface):
@ -1653,6 +1655,9 @@ class ViewBox(GraphicsWidget):
## called when the application is about to exit. ## called when the application is about to exit.
## this disables all callbacks, which might otherwise generate errors if invoked during exit. ## this disables all callbacks, which might otherwise generate errors if invoked during exit.
for k in ViewBox.AllViews: for k in ViewBox.AllViews:
if isQObjectAlive(k):
sys.stderr.write('ViewBox should be closed before application exit!')
try: try:
k.destroyed.disconnect() k.destroyed.disconnect()
except RuntimeError: ## signal is already disconnected. except RuntimeError: ## signal is already disconnected.