Commit 6c6beed7 authored by Luke Campagnola's avatar Luke Campagnola
Browse files

Fixes to make crashing less likely on PySide

Merge branch 'clear_cycles' into develop
parents ed87cffd 25e7d12f
......@@ -32,6 +32,23 @@ else:
if USE_PYSIDE:
from PySide import QtGui, QtCore, QtOpenGL, QtSvg
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__
# Make a loadUiType function like PyQt has
......@@ -78,6 +95,9 @@ else:
pass
import sip
def isQObjectAlive(obj):
return not sip.isdeleted(obj)
loadUiType = uic.loadUiType
QtCore.Signal = QtCore.pyqtSignal
......
......@@ -56,6 +56,7 @@ CONFIG_OPTIONS = {
'weaveDebug': False, ## Print full error message if weave compile fails
'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)
'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 .colormap import *
from .ptime import time
from pyqtgraph.Qt import isQObjectAlive
##############################################################
......@@ -284,7 +286,12 @@ 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:
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)
except RuntimeError: ## occurs if a python wrapper no longer has its underlying C++ object
continue
......
......@@ -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,24 @@ class PlotItem(GraphicsWidget):
"""Return the :class:`ViewBox <pyqtgraph.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).
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):
"""
......@@ -356,10 +366,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']
......
......@@ -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) and getConfigOption('crashWarning'):
sys.stderr.write('Warning: ViewBox should be closed before application exit.\n')
try:
k.destroyed.disconnect()
except RuntimeError: ## signal is already disconnected.
......
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()
\ No newline at end of file
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)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment