Moved emits after all method state updates since PySide2 immediately executes signals.

Pull request #907 addressed a specific case where a signal was emitted before a state update.
If an application's slot then calls back into the instance, the instance was in an inconsistent
state.  This commit audits and fixes similar issues throughout the pyqtgraph library.  This
commit fixes several latent issues:

* SignalProxy: flush -> sigDelayed -> signalReceived would have incorrectly resulted in timer.stop().
* ViewBox: resizeEvent -> sigStateChange -> background state
* ViewBox: setRange -> sigStateChange -> autoranging not updated correctly
* ViewBox: updateMatrix -> sigTransformChanged -> any _matrixNeedsUpdate = True -> ignored
* Parameter: Child may have missed state tree messages on insert or received extra on remove
* GraphicsView: updateMatrix -> sigDeviceRangeChanged/sigDeviceTransformChange -> before propagated to locked viewports.
This commit is contained in:
Matt Liberty 2019-06-01 16:28:23 -04:00
parent eb90616ae2
commit c52382c3b9
10 changed files with 33 additions and 33 deletions

View File

@ -67,11 +67,11 @@ class SignalProxy(QtCore.QObject):
"""If there is a signal queued up, send it now.""" """If there is a signal queued up, send it now."""
if self.args is None or self.block: if self.args is None or self.block:
return False return False
#self.emit(self.signal, *self.args) args, self.args = self.args, None
self.sigDelayed.emit(self.args)
self.args = None
self.timer.stop() self.timer.stop()
self.lastFlushTime = time() self.lastFlushTime = time()
#self.emit(self.signal, *self.args)
self.sigDelayed.emit(args)
return True return True
def disconnect(self): def disconnect(self):

View File

@ -346,9 +346,9 @@ class DockLabel(VerticalLabel):
ev.accept() ev.accept()
def mouseReleaseEvent(self, ev): def mouseReleaseEvent(self, ev):
ev.accept()
if not self.startedDrag: if not self.startedDrag:
self.sigClicked.emit(self, ev) self.sigClicked.emit(self, ev)
ev.accept()
def mouseDoubleClickEvent(self, ev): def mouseDoubleClickEvent(self, ev):
if ev.button() == QtCore.Qt.LeftButton: if ev.button() == QtCore.Qt.LeftButton:

View File

@ -503,8 +503,8 @@ class Flowchart(Node):
finally: finally:
self.blockSignals(False) self.blockSignals(False)
self.sigChartLoaded.emit()
self.outputChanged() self.outputChanged()
self.sigChartLoaded.emit()
self.sigStateChanged.emit() self.sigStateChanged.emit()
def loadFile(self, fileName=None, startDir=None): def loadFile(self, fileName=None, startDir=None):

View File

@ -205,8 +205,8 @@ class HistogramLUTItem(GraphicsWidget):
def regionChanging(self): def regionChanging(self):
if self.imageItem() is not None: if self.imageItem() is not None:
self.imageItem().setLevels(self.getLevels()) self.imageItem().setLevels(self.getLevels())
self.sigLevelsChanged.emit(self)
self.update() self.update()
self.sigLevelsChanged.emit(self)
def imageChanged(self, autoLevel=False, autoRange=False): def imageChanged(self, autoLevel=False, autoRange=False):
if self.imageItem() is None: if self.imageItem() is None:

View File

@ -711,10 +711,10 @@ class ROI(GraphicsObject):
if hover: if hover:
self.setMouseHover(True) self.setMouseHover(True)
self.sigHoverEvent.emit(self)
ev.acceptClicks(QtCore.Qt.LeftButton) ## If the ROI is hilighted, we should accept all clicks to avoid confusion. ev.acceptClicks(QtCore.Qt.LeftButton) ## If the ROI is hilighted, we should accept all clicks to avoid confusion.
ev.acceptClicks(QtCore.Qt.RightButton) ev.acceptClicks(QtCore.Qt.RightButton)
ev.acceptClicks(QtCore.Qt.MidButton) ev.acceptClicks(QtCore.Qt.MidButton)
self.sigHoverEvent.emit(self)
else: else:
self.setMouseHover(False) self.setMouseHover(False)

View File

@ -834,8 +834,8 @@ class ScatterPlotItem(GraphicsObject):
pts = self.pointsAt(ev.pos()) pts = self.pointsAt(ev.pos())
if len(pts) > 0: if len(pts) > 0:
self.ptsClicked = pts self.ptsClicked = pts
self.sigClicked.emit(self, self.ptsClicked)
ev.accept() ev.accept()
self.sigClicked.emit(self, self.ptsClicked)
else: else:
#print "no spots" #print "no spots"
ev.ignore() ev.ignore()

View File

@ -427,8 +427,8 @@ class ViewBox(GraphicsWidget):
self.updateAutoRange() self.updateAutoRange()
self.updateViewRange() self.updateViewRange()
self._matrixNeedsUpdate = True self._matrixNeedsUpdate = True
self.sigStateChanged.emit(self)
self.background.setRect(self.rect()) self.background.setRect(self.rect())
self.sigStateChanged.emit(self)
self.sigResized.emit(self) self.sigResized.emit(self)
def viewRange(self): def viewRange(self):
@ -561,18 +561,18 @@ class ViewBox(GraphicsWidget):
# If nothing has changed, we are done. # If nothing has changed, we are done.
if any(changed): if any(changed):
self.sigStateChanged.emit(self)
# Update target rect for debugging # Update target rect for debugging
if self.target.isVisible(): if self.target.isVisible():
self.target.setRect(self.mapRectFromItem(self.childGroup, self.targetRect())) self.target.setRect(self.mapRectFromItem(self.childGroup, self.targetRect()))
# If ortho axes have auto-visible-only, update them now # If ortho axes have auto-visible-only, update them now
# Note that aspect ratio constraints and auto-visible probably do not work together.. # Note that aspect ratio constraints and auto-visible probably do not work together..
if changed[0] and self.state['autoVisibleOnly'][1] and (self.state['autoRange'][0] is not False): if changed[0] and self.state['autoVisibleOnly'][1] and (self.state['autoRange'][0] is not False):
self._autoRangeNeedsUpdate = True self._autoRangeNeedsUpdate = True
elif changed[1] and self.state['autoVisibleOnly'][0] and (self.state['autoRange'][1] is not False): elif changed[1] and self.state['autoVisibleOnly'][0] and (self.state['autoRange'][1] is not False):
self._autoRangeNeedsUpdate = True self._autoRangeNeedsUpdate = True
self.sigStateChanged.emit(self)
def setYRange(self, min, max, padding=None, update=True): def setYRange(self, min, max, padding=None, update=True):
""" """
@ -1156,8 +1156,8 @@ class ViewBox(GraphicsWidget):
self._resetTarget() self._resetTarget()
self.scaleBy(s, center) self.scaleBy(s, center)
self.sigRangeChangedManually.emit(mask)
ev.accept() ev.accept()
self.sigRangeChangedManually.emit(mask)
def mouseClickEvent(self, ev): def mouseClickEvent(self, ev):
if ev.button() == QtCore.Qt.RightButton and self.menuEnabled(): if ev.button() == QtCore.Qt.RightButton and self.menuEnabled():
@ -1498,14 +1498,8 @@ class ViewBox(GraphicsWidget):
if any(changed): if any(changed):
self._matrixNeedsUpdate = True self._matrixNeedsUpdate = True
# emit range change signals
if changed[0]:
self.sigXRangeChanged.emit(self, tuple(self.state['viewRange'][0]))
if changed[1]:
self.sigYRangeChanged.emit(self, tuple(self.state['viewRange'][1]))
self.sigRangeChanged.emit(self, self.state['viewRange'])
self.update() self.update()
# Inform linked views that the range has changed # Inform linked views that the range has changed
for ax in [0, 1]: for ax in [0, 1]:
if not changed[ax]: if not changed[ax]:
@ -1514,6 +1508,13 @@ class ViewBox(GraphicsWidget):
if link is not None: if link is not None:
link.linkedViewChanged(self, ax) link.linkedViewChanged(self, ax)
# emit range change signals
if changed[0]:
self.sigXRangeChanged.emit(self, tuple(self.state['viewRange'][0]))
if changed[1]:
self.sigYRangeChanged.emit(self, tuple(self.state['viewRange'][1]))
self.sigRangeChanged.emit(self, self.state['viewRange'])
def updateMatrix(self, changed=None): def updateMatrix(self, changed=None):
if not self._matrixNeedsUpdate: if not self._matrixNeedsUpdate:
return return
@ -1541,9 +1542,9 @@ class ViewBox(GraphicsWidget):
m.translate(-st[0], -st[1]) m.translate(-st[0], -st[1])
self.childGroup.setTransform(m) self.childGroup.setTransform(m)
self._matrixNeedsUpdate = False
self.sigTransformChanged.emit(self) ## segfaults here: 1 self.sigTransformChanged.emit(self) ## segfaults here: 1
self._matrixNeedsUpdate = False
def paint(self, p, opt, widget): def paint(self, p, opt, widget):
self.checkSceneChange() self.checkSceneChange()

View File

@ -559,8 +559,8 @@ class Parameter(QtCore.QObject):
self.childs.insert(pos, child) self.childs.insert(pos, child)
child.parentChanged(self) child.parentChanged(self)
self.sigChildAdded.emit(self, child, pos)
child.sigTreeStateChanged.connect(self.treeStateChanged) child.sigTreeStateChanged.connect(self.treeStateChanged)
self.sigChildAdded.emit(self, child, pos)
return child return child
def removeChild(self, child): def removeChild(self, child):
@ -571,11 +571,11 @@ class Parameter(QtCore.QObject):
del self.names[name] del self.names[name]
self.childs.pop(self.childs.index(child)) self.childs.pop(self.childs.index(child))
child.parentChanged(None) child.parentChanged(None)
self.sigChildRemoved.emit(self, child)
try: try:
child.sigTreeStateChanged.disconnect(self.treeStateChanged) child.sigTreeStateChanged.disconnect(self.treeStateChanged)
except (TypeError, RuntimeError): ## already disconnected except (TypeError, RuntimeError): ## already disconnected
pass pass
self.sigChildRemoved.emit(self, child)
def clearChildren(self): def clearChildren(self):
"""Remove all child parameters.""" """Remove all child parameters."""

View File

@ -50,11 +50,11 @@ class ColorButton(QtGui.QPushButton):
def setColor(self, color, finished=True): def setColor(self, color, finished=True):
"""Sets the button's color and emits both sigColorChanged and sigColorChanging.""" """Sets the button's color and emits both sigColorChanged and sigColorChanging."""
self._color = functions.mkColor(color) self._color = functions.mkColor(color)
self.update()
if finished: if finished:
self.sigColorChanged.emit(self) self.sigColorChanged.emit(self)
else: else:
self.sigColorChanging.emit(self) self.sigColorChanging.emit(self)
self.update()
def selectColor(self): def selectColor(self):
self.origColor = self.color() self.origColor = self.color()

View File

@ -227,12 +227,12 @@ class GraphicsView(QtGui.QGraphicsView):
else: else:
self.fitInView(self.range, QtCore.Qt.IgnoreAspectRatio) self.fitInView(self.range, QtCore.Qt.IgnoreAspectRatio)
self.sigDeviceRangeChanged.emit(self, self.range)
self.sigDeviceTransformChanged.emit(self)
if propagate: if propagate:
for v in self.lockedViewports: for v in self.lockedViewports:
v.setXRange(self.range, padding=0) v.setXRange(self.range, padding=0)
self.sigDeviceRangeChanged.emit(self, self.range)
self.sigDeviceTransformChanged.emit(self)
def viewRect(self): def viewRect(self):
"""Return the boundaries of the view in scene coordinates""" """Return the boundaries of the view in scene coordinates"""
@ -262,7 +262,6 @@ class GraphicsView(QtGui.QGraphicsView):
h = self.range.height() / scale[1] h = self.range.height() / scale[1]
self.range = QtCore.QRectF(center.x() - (center.x()-self.range.left()) / scale[0], center.y() - (center.y()-self.range.top()) /scale[1], w, h) self.range = QtCore.QRectF(center.x() - (center.x()-self.range.left()) / scale[0], center.y() - (center.y()-self.range.top()) /scale[1], w, h)
self.updateMatrix() self.updateMatrix()
self.sigScaleChanged.emit(self) self.sigScaleChanged.emit(self)