Fixed a few exit crashes, added unit tests to cover them
This commit is contained in:
parent
2bf4a0eb7b
commit
f6ded808ef
@ -84,8 +84,8 @@ class GraphicsScene(QtGui.QGraphicsScene):
|
||||
cls._addressCache[sip.unwrapinstance(sip.cast(obj, QtGui.QGraphicsItem))] = obj
|
||||
|
||||
|
||||
def __init__(self, clickRadius=2, moveDistance=5):
|
||||
QtGui.QGraphicsScene.__init__(self)
|
||||
def __init__(self, clickRadius=2, moveDistance=5, parent=None):
|
||||
QtGui.QGraphicsScene.__init__(self, parent)
|
||||
self.setClickRadius(clickRadius)
|
||||
self.setMoveDistance(moveDistance)
|
||||
self.exportDirectory = None
|
||||
|
@ -270,7 +270,12 @@ from .Qt import isQObjectAlive
|
||||
|
||||
## Attempts to work around exit crashes:
|
||||
import atexit
|
||||
_cleanupCalled = False
|
||||
def cleanup():
|
||||
global _cleanupCalled
|
||||
if _cleanupCalled:
|
||||
return
|
||||
|
||||
if not getConfigOption('exitCleanup'):
|
||||
return
|
||||
|
||||
@ -295,8 +300,22 @@ def cleanup():
|
||||
s.addItem(o)
|
||||
except RuntimeError: ## occurs if a python wrapper no longer has its underlying C++ object
|
||||
continue
|
||||
_cleanupCalled = True
|
||||
|
||||
atexit.register(cleanup)
|
||||
|
||||
# Call cleanup when QApplication quits. This is necessary because sometimes
|
||||
# the QApplication will quit before the atexit callbacks are invoked.
|
||||
# Note: cannot connect this function until QApplication has been created, so
|
||||
# instead we have GraphicsView.__init__ call this for us.
|
||||
_cleanupConnected = False
|
||||
def _connectCleanup():
|
||||
global _cleanupConnected
|
||||
if _cleanupConnected:
|
||||
return
|
||||
QtGui.QApplication.instance().aboutToQuit.connect(cleanup)
|
||||
_cleanupConnected = True
|
||||
|
||||
|
||||
## Optional function for exiting immediately (with some manual teardown)
|
||||
def exit():
|
||||
|
@ -49,7 +49,7 @@ class HistogramLUTItem(GraphicsWidget):
|
||||
self.setLayout(self.layout)
|
||||
self.layout.setContentsMargins(1,1,1,1)
|
||||
self.layout.setSpacing(0)
|
||||
self.vb = ViewBox()
|
||||
self.vb = ViewBox(parent=self)
|
||||
self.vb.setMaximumWidth(152)
|
||||
self.vb.setMinimumWidth(45)
|
||||
self.vb.setMouseEnabled(x=False, y=True)
|
||||
@ -59,7 +59,7 @@ class HistogramLUTItem(GraphicsWidget):
|
||||
self.region = LinearRegionItem([0, 1], LinearRegionItem.Horizontal)
|
||||
self.region.setZValue(1000)
|
||||
self.vb.addItem(self.region)
|
||||
self.axis = AxisItem('left', linkView=self.vb, maxTickLength=-10)
|
||||
self.axis = AxisItem('left', linkView=self.vb, maxTickLength=-10, parent=self)
|
||||
self.layout.addItem(self.axis, 0, 0)
|
||||
self.layout.addItem(self.vb, 0, 1)
|
||||
self.layout.addItem(self.gradient, 0, 2)
|
||||
|
@ -145,7 +145,7 @@ class PlotItem(GraphicsWidget):
|
||||
self.layout.setVerticalSpacing(0)
|
||||
|
||||
if viewBox is None:
|
||||
viewBox = ViewBox()
|
||||
viewBox = ViewBox(parent=self)
|
||||
self.vb = viewBox
|
||||
self.vb.sigStateChanged.connect(self.viewStateChanged)
|
||||
self.setMenuEnabled(enableMenu, enableMenu) ## en/disable plotitem and viewbox menus
|
||||
@ -168,14 +168,14 @@ class PlotItem(GraphicsWidget):
|
||||
axisItems = {}
|
||||
self.axes = {}
|
||||
for k, pos in (('top', (1,1)), ('bottom', (3,1)), ('left', (2,0)), ('right', (2,2))):
|
||||
axis = axisItems.get(k, AxisItem(orientation=k))
|
||||
axis = axisItems.get(k, AxisItem(orientation=k, parent=self))
|
||||
axis.linkToView(self.vb)
|
||||
self.axes[k] = {'item': axis, 'pos': pos}
|
||||
self.layout.addItem(axis, *pos)
|
||||
axis.setZValue(-1000)
|
||||
axis.setFlag(axis.ItemNegativeZStacksBehindParent)
|
||||
|
||||
self.titleLabel = LabelItem('', size='11pt')
|
||||
self.titleLabel = LabelItem('', size='11pt', parent=self)
|
||||
self.layout.addItem(self.titleLabel, 0, 1)
|
||||
self.setTitle(None) ## hide
|
||||
|
||||
|
@ -1696,6 +1696,8 @@ class ViewBox(GraphicsWidget):
|
||||
def forgetView(vid, name):
|
||||
if ViewBox is None: ## can happen as python is shutting down
|
||||
return
|
||||
if QtGui.QApplication.instance() is None:
|
||||
return
|
||||
## Called with ID and name of view (the view itself is no longer available)
|
||||
for v in list(ViewBox.AllViews.keys()):
|
||||
if id(v) == vid:
|
||||
|
38
pyqtgraph/tests/test_exit_crash.py
Normal file
38
pyqtgraph/tests/test_exit_crash.py
Normal file
@ -0,0 +1,38 @@
|
||||
import os, sys, subprocess, tempfile
|
||||
import pyqtgraph as pg
|
||||
|
||||
|
||||
code = """
|
||||
import sys
|
||||
sys.path.insert(0, '{path}')
|
||||
import pyqtgraph as pg
|
||||
app = pg.mkQApp()
|
||||
w = pg.{classname}({args})
|
||||
"""
|
||||
|
||||
|
||||
def test_exit_crash():
|
||||
# For each Widget subclass, run a simple python script that creates an
|
||||
# instance and then shuts down. The intent is to check for segmentation
|
||||
# faults when each script exits.
|
||||
tmp = tempfile.mktemp(".py")
|
||||
path = os.path.dirname(pg.__file__)
|
||||
|
||||
initArgs = {
|
||||
'CheckTable': "[]",
|
||||
'ProgressDialog': '"msg"',
|
||||
'VerticalLabel': '"msg"',
|
||||
}
|
||||
|
||||
for name in dir(pg):
|
||||
obj = getattr(pg, name)
|
||||
if not isinstance(obj, type) or not issubclass(obj, pg.QtGui.QWidget):
|
||||
continue
|
||||
|
||||
print name
|
||||
argstr = initArgs.get(name, "")
|
||||
open(tmp, 'w').write(code.format(path=path, classname=name, args=argstr))
|
||||
proc = subprocess.Popen([sys.executable, tmp])
|
||||
assert proc.wait() == 0
|
||||
|
||||
os.remove(tmp)
|
@ -71,6 +71,13 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
|
||||
QtGui.QGraphicsView.__init__(self, parent)
|
||||
|
||||
# This connects a cleanup function to QApplication.aboutToQuit. It is
|
||||
# called from here because we have no good way to react when the
|
||||
# QApplication is created by the user.
|
||||
# See pyqtgraph.__init__.py
|
||||
from .. import _connectCleanup
|
||||
_connectCleanup()
|
||||
|
||||
if useOpenGL is None:
|
||||
useOpenGL = getConfigOption('useOpenGL')
|
||||
|
||||
@ -102,7 +109,8 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
self.currentItem = None
|
||||
self.clearMouse()
|
||||
self.updateMatrix()
|
||||
self.sceneObj = GraphicsScene()
|
||||
# GraphicsScene must have parent or expect crashes!
|
||||
self.sceneObj = GraphicsScene(parent=self)
|
||||
self.setScene(self.sceneObj)
|
||||
|
||||
## Workaround for PySide crash
|
||||
@ -143,7 +151,6 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
|
||||
def paintEvent(self, ev):
|
||||
self.scene().prepareForPaint()
|
||||
#print "GV: paint", ev.rect()
|
||||
return QtGui.QGraphicsView.paintEvent(self, ev)
|
||||
|
||||
def render(self, *args, **kwds):
|
||||
|
Loading…
Reference in New Issue
Block a user