Merge branch 'crash-fixes' into develop
This commit is contained in:
commit
87453a2e2a
@ -97,6 +97,8 @@ pyqtgraph-0.9.9 [unreleased]
|
|||||||
- Removed a few cyclic references
|
- Removed a few cyclic references
|
||||||
- Fixed Parameter 'readonly' option for bool, color, and text parameter types
|
- Fixed Parameter 'readonly' option for bool, color, and text parameter types
|
||||||
- Fixed alpha on GLScatterPlotItem spots (formerly maxed out at alpha=200)
|
- Fixed alpha on GLScatterPlotItem spots (formerly maxed out at alpha=200)
|
||||||
|
- Fixed a few bugs causing exit crashes
|
||||||
|
|
||||||
|
|
||||||
pyqtgraph-0.9.8 2013-11-24
|
pyqtgraph-0.9.8 2013-11-24
|
||||||
|
|
||||||
|
@ -84,8 +84,8 @@ class GraphicsScene(QtGui.QGraphicsScene):
|
|||||||
cls._addressCache[sip.unwrapinstance(sip.cast(obj, QtGui.QGraphicsItem))] = obj
|
cls._addressCache[sip.unwrapinstance(sip.cast(obj, QtGui.QGraphicsItem))] = obj
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, clickRadius=2, moveDistance=5):
|
def __init__(self, clickRadius=2, moveDistance=5, parent=None):
|
||||||
QtGui.QGraphicsScene.__init__(self)
|
QtGui.QGraphicsScene.__init__(self, parent)
|
||||||
self.setClickRadius(clickRadius)
|
self.setClickRadius(clickRadius)
|
||||||
self.setMoveDistance(moveDistance)
|
self.setMoveDistance(moveDistance)
|
||||||
self.exportDirectory = None
|
self.exportDirectory = None
|
||||||
|
@ -270,7 +270,12 @@ from .Qt import isQObjectAlive
|
|||||||
|
|
||||||
## Attempts to work around exit crashes:
|
## Attempts to work around exit crashes:
|
||||||
import atexit
|
import atexit
|
||||||
|
_cleanupCalled = False
|
||||||
def cleanup():
|
def cleanup():
|
||||||
|
global _cleanupCalled
|
||||||
|
if _cleanupCalled:
|
||||||
|
return
|
||||||
|
|
||||||
if not getConfigOption('exitCleanup'):
|
if not getConfigOption('exitCleanup'):
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -295,8 +300,22 @@ def cleanup():
|
|||||||
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
|
||||||
|
_cleanupCalled = True
|
||||||
|
|
||||||
atexit.register(cleanup)
|
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)
|
## Optional function for exiting immediately (with some manual teardown)
|
||||||
def exit():
|
def exit():
|
||||||
|
@ -49,7 +49,7 @@ class HistogramLUTItem(GraphicsWidget):
|
|||||||
self.setLayout(self.layout)
|
self.setLayout(self.layout)
|
||||||
self.layout.setContentsMargins(1,1,1,1)
|
self.layout.setContentsMargins(1,1,1,1)
|
||||||
self.layout.setSpacing(0)
|
self.layout.setSpacing(0)
|
||||||
self.vb = ViewBox()
|
self.vb = ViewBox(parent=self)
|
||||||
self.vb.setMaximumWidth(152)
|
self.vb.setMaximumWidth(152)
|
||||||
self.vb.setMinimumWidth(45)
|
self.vb.setMinimumWidth(45)
|
||||||
self.vb.setMouseEnabled(x=False, y=True)
|
self.vb.setMouseEnabled(x=False, y=True)
|
||||||
@ -59,7 +59,7 @@ class HistogramLUTItem(GraphicsWidget):
|
|||||||
self.region = LinearRegionItem([0, 1], LinearRegionItem.Horizontal)
|
self.region = LinearRegionItem([0, 1], LinearRegionItem.Horizontal)
|
||||||
self.region.setZValue(1000)
|
self.region.setZValue(1000)
|
||||||
self.vb.addItem(self.region)
|
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.axis, 0, 0)
|
||||||
self.layout.addItem(self.vb, 0, 1)
|
self.layout.addItem(self.vb, 0, 1)
|
||||||
self.layout.addItem(self.gradient, 0, 2)
|
self.layout.addItem(self.gradient, 0, 2)
|
||||||
|
@ -145,7 +145,7 @@ class PlotItem(GraphicsWidget):
|
|||||||
self.layout.setVerticalSpacing(0)
|
self.layout.setVerticalSpacing(0)
|
||||||
|
|
||||||
if viewBox is None:
|
if viewBox is None:
|
||||||
viewBox = ViewBox()
|
viewBox = ViewBox(parent=self)
|
||||||
self.vb = viewBox
|
self.vb = viewBox
|
||||||
self.vb.sigStateChanged.connect(self.viewStateChanged)
|
self.vb.sigStateChanged.connect(self.viewStateChanged)
|
||||||
self.setMenuEnabled(enableMenu, enableMenu) ## en/disable plotitem and viewbox menus
|
self.setMenuEnabled(enableMenu, enableMenu) ## en/disable plotitem and viewbox menus
|
||||||
@ -168,14 +168,14 @@ class PlotItem(GraphicsWidget):
|
|||||||
axisItems = {}
|
axisItems = {}
|
||||||
self.axes = {}
|
self.axes = {}
|
||||||
for k, pos in (('top', (1,1)), ('bottom', (3,1)), ('left', (2,0)), ('right', (2,2))):
|
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)
|
axis.linkToView(self.vb)
|
||||||
self.axes[k] = {'item': axis, 'pos': pos}
|
self.axes[k] = {'item': axis, 'pos': pos}
|
||||||
self.layout.addItem(axis, *pos)
|
self.layout.addItem(axis, *pos)
|
||||||
axis.setZValue(-1000)
|
axis.setZValue(-1000)
|
||||||
axis.setFlag(axis.ItemNegativeZStacksBehindParent)
|
axis.setFlag(axis.ItemNegativeZStacksBehindParent)
|
||||||
|
|
||||||
self.titleLabel = LabelItem('', size='11pt')
|
self.titleLabel = LabelItem('', size='11pt', parent=self)
|
||||||
self.layout.addItem(self.titleLabel, 0, 1)
|
self.layout.addItem(self.titleLabel, 0, 1)
|
||||||
self.setTitle(None) ## hide
|
self.setTitle(None) ## hide
|
||||||
|
|
||||||
|
@ -1696,6 +1696,8 @@ class ViewBox(GraphicsWidget):
|
|||||||
def forgetView(vid, name):
|
def forgetView(vid, name):
|
||||||
if ViewBox is None: ## can happen as python is shutting down
|
if ViewBox is None: ## can happen as python is shutting down
|
||||||
return
|
return
|
||||||
|
if QtGui.QApplication.instance() is None:
|
||||||
|
return
|
||||||
## Called with ID and name of view (the view itself is no longer available)
|
## Called with ID and name of view (the view itself is no longer available)
|
||||||
for v in list(ViewBox.AllViews.keys()):
|
for v in list(ViewBox.AllViews.keys()):
|
||||||
if id(v) == vid:
|
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)
|
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:
|
if useOpenGL is None:
|
||||||
useOpenGL = getConfigOption('useOpenGL')
|
useOpenGL = getConfigOption('useOpenGL')
|
||||||
|
|
||||||
@ -102,7 +109,8 @@ class GraphicsView(QtGui.QGraphicsView):
|
|||||||
self.currentItem = None
|
self.currentItem = None
|
||||||
self.clearMouse()
|
self.clearMouse()
|
||||||
self.updateMatrix()
|
self.updateMatrix()
|
||||||
self.sceneObj = GraphicsScene()
|
# GraphicsScene must have parent or expect crashes!
|
||||||
|
self.sceneObj = GraphicsScene(parent=self)
|
||||||
self.setScene(self.sceneObj)
|
self.setScene(self.sceneObj)
|
||||||
|
|
||||||
## Workaround for PySide crash
|
## Workaround for PySide crash
|
||||||
@ -143,7 +151,6 @@ class GraphicsView(QtGui.QGraphicsView):
|
|||||||
|
|
||||||
def paintEvent(self, ev):
|
def paintEvent(self, ev):
|
||||||
self.scene().prepareForPaint()
|
self.scene().prepareForPaint()
|
||||||
#print "GV: paint", ev.rect()
|
|
||||||
return QtGui.QGraphicsView.paintEvent(self, ev)
|
return QtGui.QGraphicsView.paintEvent(self, ev)
|
||||||
|
|
||||||
def render(self, *args, **kwds):
|
def render(self, *args, **kwds):
|
||||||
|
Loading…
Reference in New Issue
Block a user