From c52382c3b982205588cd903f72c2fddd45658701 Mon Sep 17 00:00:00 2001 From: Matt Liberty Date: Sat, 1 Jun 2019 16:28:23 -0400 Subject: [PATCH] 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. --- pyqtgraph/SignalProxy.py | 6 ++-- pyqtgraph/dockarea/Dock.py | 2 +- pyqtgraph/flowchart/Flowchart.py | 2 +- pyqtgraph/graphicsItems/HistogramLUTItem.py | 2 +- pyqtgraph/graphicsItems/ROI.py | 2 +- pyqtgraph/graphicsItems/ScatterPlotItem.py | 2 +- pyqtgraph/graphicsItems/ViewBox/ViewBox.py | 37 +++++++++++---------- pyqtgraph/parametertree/Parameter.py | 4 +-- pyqtgraph/widgets/ColorButton.py | 2 +- pyqtgraph/widgets/GraphicsView.py | 7 ++-- 10 files changed, 33 insertions(+), 33 deletions(-) diff --git a/pyqtgraph/SignalProxy.py b/pyqtgraph/SignalProxy.py index d36282fa..7463dfc3 100644 --- a/pyqtgraph/SignalProxy.py +++ b/pyqtgraph/SignalProxy.py @@ -67,11 +67,11 @@ class SignalProxy(QtCore.QObject): """If there is a signal queued up, send it now.""" if self.args is None or self.block: return False - #self.emit(self.signal, *self.args) - self.sigDelayed.emit(self.args) - self.args = None + args, self.args = self.args, None self.timer.stop() self.lastFlushTime = time() + #self.emit(self.signal, *self.args) + self.sigDelayed.emit(args) return True def disconnect(self): diff --git a/pyqtgraph/dockarea/Dock.py b/pyqtgraph/dockarea/Dock.py index 1d946062..ddeb0c4a 100644 --- a/pyqtgraph/dockarea/Dock.py +++ b/pyqtgraph/dockarea/Dock.py @@ -346,9 +346,9 @@ class DockLabel(VerticalLabel): ev.accept() def mouseReleaseEvent(self, ev): + ev.accept() if not self.startedDrag: self.sigClicked.emit(self, ev) - ev.accept() def mouseDoubleClickEvent(self, ev): if ev.button() == QtCore.Qt.LeftButton: diff --git a/pyqtgraph/flowchart/Flowchart.py b/pyqtgraph/flowchart/Flowchart.py index ae03d4c2..5aeeac38 100644 --- a/pyqtgraph/flowchart/Flowchart.py +++ b/pyqtgraph/flowchart/Flowchart.py @@ -503,8 +503,8 @@ class Flowchart(Node): finally: self.blockSignals(False) - self.sigChartLoaded.emit() self.outputChanged() + self.sigChartLoaded.emit() self.sigStateChanged.emit() def loadFile(self, fileName=None, startDir=None): diff --git a/pyqtgraph/graphicsItems/HistogramLUTItem.py b/pyqtgraph/graphicsItems/HistogramLUTItem.py index 7272aef3..687c2e3f 100644 --- a/pyqtgraph/graphicsItems/HistogramLUTItem.py +++ b/pyqtgraph/graphicsItems/HistogramLUTItem.py @@ -205,8 +205,8 @@ class HistogramLUTItem(GraphicsWidget): def regionChanging(self): if self.imageItem() is not None: self.imageItem().setLevels(self.getLevels()) - self.sigLevelsChanged.emit(self) self.update() + self.sigLevelsChanged.emit(self) def imageChanged(self, autoLevel=False, autoRange=False): if self.imageItem() is None: diff --git a/pyqtgraph/graphicsItems/ROI.py b/pyqtgraph/graphicsItems/ROI.py index fa2bcf5f..48f30880 100644 --- a/pyqtgraph/graphicsItems/ROI.py +++ b/pyqtgraph/graphicsItems/ROI.py @@ -711,10 +711,10 @@ class ROI(GraphicsObject): if hover: 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.RightButton) ev.acceptClicks(QtCore.Qt.MidButton) + self.sigHoverEvent.emit(self) else: self.setMouseHover(False) diff --git a/pyqtgraph/graphicsItems/ScatterPlotItem.py b/pyqtgraph/graphicsItems/ScatterPlotItem.py index 89bb5b98..67fafd83 100644 --- a/pyqtgraph/graphicsItems/ScatterPlotItem.py +++ b/pyqtgraph/graphicsItems/ScatterPlotItem.py @@ -834,8 +834,8 @@ class ScatterPlotItem(GraphicsObject): pts = self.pointsAt(ev.pos()) if len(pts) > 0: self.ptsClicked = pts - self.sigClicked.emit(self, self.ptsClicked) ev.accept() + self.sigClicked.emit(self, self.ptsClicked) else: #print "no spots" ev.ignore() diff --git a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py index e85796c9..b874a3c4 100644 --- a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py +++ b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py @@ -427,8 +427,8 @@ class ViewBox(GraphicsWidget): self.updateAutoRange() self.updateViewRange() self._matrixNeedsUpdate = True - self.sigStateChanged.emit(self) self.background.setRect(self.rect()) + self.sigStateChanged.emit(self) self.sigResized.emit(self) def viewRange(self): @@ -561,18 +561,18 @@ class ViewBox(GraphicsWidget): # If nothing has changed, we are done. if any(changed): - self.sigStateChanged.emit(self) - # Update target rect for debugging if self.target.isVisible(): self.target.setRect(self.mapRectFromItem(self.childGroup, self.targetRect())) - # If ortho axes have auto-visible-only, update them now - # 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): - self._autoRangeNeedsUpdate = True - elif changed[1] and self.state['autoVisibleOnly'][0] and (self.state['autoRange'][1] is not False): - self._autoRangeNeedsUpdate = True + # If ortho axes have auto-visible-only, update them now + # 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): + self._autoRangeNeedsUpdate = True + elif changed[1] and self.state['autoVisibleOnly'][0] and (self.state['autoRange'][1] is not False): + self._autoRangeNeedsUpdate = True + + self.sigStateChanged.emit(self) def setYRange(self, min, max, padding=None, update=True): """ @@ -1156,8 +1156,8 @@ class ViewBox(GraphicsWidget): self._resetTarget() self.scaleBy(s, center) - self.sigRangeChangedManually.emit(mask) ev.accept() + self.sigRangeChangedManually.emit(mask) def mouseClickEvent(self, ev): if ev.button() == QtCore.Qt.RightButton and self.menuEnabled(): @@ -1498,14 +1498,8 @@ class ViewBox(GraphicsWidget): if any(changed): 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() - + # Inform linked views that the range has changed for ax in [0, 1]: if not changed[ax]: @@ -1514,6 +1508,13 @@ class ViewBox(GraphicsWidget): if link is not None: 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): if not self._matrixNeedsUpdate: return @@ -1541,9 +1542,9 @@ class ViewBox(GraphicsWidget): m.translate(-st[0], -st[1]) self.childGroup.setTransform(m) + self._matrixNeedsUpdate = False self.sigTransformChanged.emit(self) ## segfaults here: 1 - self._matrixNeedsUpdate = False def paint(self, p, opt, widget): self.checkSceneChange() diff --git a/pyqtgraph/parametertree/Parameter.py b/pyqtgraph/parametertree/Parameter.py index df6b1492..654a33db 100644 --- a/pyqtgraph/parametertree/Parameter.py +++ b/pyqtgraph/parametertree/Parameter.py @@ -559,8 +559,8 @@ class Parameter(QtCore.QObject): self.childs.insert(pos, child) child.parentChanged(self) - self.sigChildAdded.emit(self, child, pos) child.sigTreeStateChanged.connect(self.treeStateChanged) + self.sigChildAdded.emit(self, child, pos) return child def removeChild(self, child): @@ -571,11 +571,11 @@ class Parameter(QtCore.QObject): del self.names[name] self.childs.pop(self.childs.index(child)) child.parentChanged(None) - self.sigChildRemoved.emit(self, child) try: child.sigTreeStateChanged.disconnect(self.treeStateChanged) except (TypeError, RuntimeError): ## already disconnected pass + self.sigChildRemoved.emit(self, child) def clearChildren(self): """Remove all child parameters.""" diff --git a/pyqtgraph/widgets/ColorButton.py b/pyqtgraph/widgets/ColorButton.py index a0bb0c8e..43dd16f6 100644 --- a/pyqtgraph/widgets/ColorButton.py +++ b/pyqtgraph/widgets/ColorButton.py @@ -50,11 +50,11 @@ class ColorButton(QtGui.QPushButton): def setColor(self, color, finished=True): """Sets the button's color and emits both sigColorChanged and sigColorChanging.""" self._color = functions.mkColor(color) + self.update() if finished: self.sigColorChanged.emit(self) else: self.sigColorChanging.emit(self) - self.update() def selectColor(self): self.origColor = self.color() diff --git a/pyqtgraph/widgets/GraphicsView.py b/pyqtgraph/widgets/GraphicsView.py index b81eab9d..7b8c5986 100644 --- a/pyqtgraph/widgets/GraphicsView.py +++ b/pyqtgraph/widgets/GraphicsView.py @@ -227,12 +227,12 @@ class GraphicsView(QtGui.QGraphicsView): else: self.fitInView(self.range, QtCore.Qt.IgnoreAspectRatio) - self.sigDeviceRangeChanged.emit(self, self.range) - self.sigDeviceTransformChanged.emit(self) - if propagate: for v in self.lockedViewports: v.setXRange(self.range, padding=0) + + self.sigDeviceRangeChanged.emit(self, self.range) + self.sigDeviceTransformChanged.emit(self) def viewRect(self): """Return the boundaries of the view in scene coordinates""" @@ -262,7 +262,6 @@ class GraphicsView(QtGui.QGraphicsView): 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.updateMatrix() self.sigScaleChanged.emit(self)