Fixes to make crashing less likely on PySide
Merge branch 'clear_cycles' into develop
This commit is contained in:
commit
6c6beed735
@ -32,6 +32,23 @@ 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
|
||||||
|
try:
|
||||||
|
from PySide import shiboken
|
||||||
|
isQObjectAlive = shiboken.isValid
|
||||||
|
except ImportError:
|
||||||
|
def isQObjectAlive(obj):
|
||||||
|
try:
|
||||||
|
if hasattr(obj, 'parent'):
|
||||||
|
obj.parent()
|
||||||
|
elif hasattr(obj, 'parentItem'):
|
||||||
|
obj.parentItem()
|
||||||
|
else:
|
||||||
|
raise Exception("Cannot determine whether Qt object %s is still alive." % obj)
|
||||||
|
except RuntimeError:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
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 +95,9 @@ else:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
import sip
|
||||||
|
def isQObjectAlive(obj):
|
||||||
|
return not sip.isdeleted(obj)
|
||||||
loadUiType = uic.loadUiType
|
loadUiType = uic.loadUiType
|
||||||
|
|
||||||
QtCore.Signal = QtCore.pyqtSignal
|
QtCore.Signal = QtCore.pyqtSignal
|
||||||
|
@ -56,6 +56,7 @@ CONFIG_OPTIONS = {
|
|||||||
'weaveDebug': False, ## Print full error message if weave compile fails
|
'weaveDebug': False, ## Print full error message if weave compile fails
|
||||||
'exitCleanup': True, ## Attempt to work around some exit crash bugs in PyQt and PySide
|
'exitCleanup': True, ## Attempt to work around some exit crash bugs in PyQt and PySide
|
||||||
'enableExperimental': False, ## Enable experimental features (the curious can search for this key in the code)
|
'enableExperimental': False, ## Enable experimental features (the curious can search for this key in the code)
|
||||||
|
'crashWarning': False, # If True, print warnings about situations that may result in a crash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -256,6 +257,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 +286,12 @@ 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:
|
||||||
|
if getConfigOption('crashWarning'):
|
||||||
|
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
|
||||||
|
@ -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,24 @@ 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).
|
||||||
|
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):
|
||||||
|
def method(self, *args, **kwargs):
|
||||||
|
return getattr(self.vb, name)(*args, **kwargs)
|
||||||
|
method.__name__ = name
|
||||||
|
return method
|
||||||
|
|
||||||
|
locals()[m] = _create_method(m)
|
||||||
|
|
||||||
|
del _create_method
|
||||||
|
|
||||||
def setLogMode(self, x=None, y=None):
|
def setLogMode(self, x=None, y=None):
|
||||||
"""
|
"""
|
||||||
@ -356,10 +366,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']
|
||||||
|
@ -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) and getConfigOption('crashWarning'):
|
||||||
|
sys.stderr.write('Warning: ViewBox should be closed before application exit.\n')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
k.destroyed.disconnect()
|
k.destroyed.disconnect()
|
||||||
except RuntimeError: ## signal is already disconnected.
|
except RuntimeError: ## signal is already disconnected.
|
||||||
|
47
pyqtgraph/graphicsItems/tests/test_GraphicsItem.py
Normal file
47
pyqtgraph/graphicsItems/tests/test_GraphicsItem.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import gc
|
||||||
|
import weakref
|
||||||
|
try:
|
||||||
|
import faulthandler
|
||||||
|
faulthandler.enable()
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
import pyqtgraph as pg
|
||||||
|
pg.mkQApp()
|
||||||
|
|
||||||
|
def test_getViewWidget():
|
||||||
|
view = pg.PlotWidget()
|
||||||
|
vref = weakref.ref(view)
|
||||||
|
item = pg.InfiniteLine()
|
||||||
|
view.addItem(item)
|
||||||
|
assert item.getViewWidget() is view
|
||||||
|
del view
|
||||||
|
gc.collect()
|
||||||
|
assert vref() is None
|
||||||
|
assert item.getViewWidget() is None
|
||||||
|
|
||||||
|
def test_getViewWidget_deleted():
|
||||||
|
view = pg.PlotWidget()
|
||||||
|
item = pg.InfiniteLine()
|
||||||
|
view.addItem(item)
|
||||||
|
assert item.getViewWidget() is view
|
||||||
|
|
||||||
|
# Arrange to have Qt automatically delete the view widget
|
||||||
|
obj = pg.QtGui.QWidget()
|
||||||
|
view.setParent(obj)
|
||||||
|
del obj
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
|
assert not pg.Qt.isQObjectAlive(view)
|
||||||
|
assert item.getViewWidget() is None
|
||||||
|
|
||||||
|
|
||||||
|
#if __name__ == '__main__':
|
||||||
|
#view = pg.PlotItem()
|
||||||
|
#vref = weakref.ref(view)
|
||||||
|
#item = pg.InfiniteLine()
|
||||||
|
#view.addItem(item)
|
||||||
|
#del view
|
||||||
|
#gc.collect()
|
||||||
|
|
||||||
|
|
10
pyqtgraph/tests/test_qt.py
Normal file
10
pyqtgraph/tests/test_qt.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import pyqtgraph as pg
|
||||||
|
import gc
|
||||||
|
|
||||||
|
def test_isQObjectAlive():
|
||||||
|
o1 = pg.QtCore.QObject()
|
||||||
|
o2 = pg.QtCore.QObject()
|
||||||
|
o2.setParent(o1)
|
||||||
|
del o1
|
||||||
|
gc.collect()
|
||||||
|
assert not pg.Qt.isQObjectAlive(o2)
|
Loading…
Reference in New Issue
Block a user