From c51a84ae4ee52d877fe5473ebcd304c17946af15 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Mon, 13 Jul 2020 12:49:57 -0700 Subject: [PATCH 01/12] Fix console exception filtering for py3 --- pyqtgraph/console/Console.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyqtgraph/console/Console.py b/pyqtgraph/console/Console.py index 9689323f..6cf66334 100644 --- a/pyqtgraph/console/Console.py +++ b/pyqtgraph/console/Console.py @@ -447,7 +447,7 @@ class ConsoleWidget(QtGui.QWidget): filterStr = str(self.ui.filterText.text()) if filterStr != '': if isinstance(exc, Exception): - msg = exc.message + msg = traceback.format_exception_only(type(exc), exc) elif isinstance(exc, basestring): msg = exc else: From 893c85053f96f9be4b23364f6acceba0d880504c Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Mon, 13 Jul 2020 12:50:24 -0700 Subject: [PATCH 02/12] debug.ThreadTrace add support for thread names --- pyqtgraph/debug.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/pyqtgraph/debug.py b/pyqtgraph/debug.py index bc6d6895..bd2f20e7 100644 --- a/pyqtgraph/debug.py +++ b/pyqtgraph/debug.py @@ -1151,7 +1151,22 @@ class ThreadTrace(object): for id, frame in sys._current_frames().items(): if id == threading.current_thread().ident: continue - print("<< thread %d >>" % id) + + # try to determine a thread name + try: + name = threading._active.get(id, None) + except: + name = None + if name is None: + try: + # QThread._names must be manually set by thread creators. + name = QtCore.QThread._names.get(id) + except: + name = None + if name is None: + name = "???" + + print("<< thread %d \"%s\" >>" % (id, name)) traceback.print_stack(frame) print("===============================================\n") From 993871b1ae2dfb23cc34145f1b1032a440b26763 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Mon, 13 Jul 2020 12:51:03 -0700 Subject: [PATCH 03/12] InfinifteLine.setPos add support for array arg --- pyqtgraph/graphicsItems/InfiniteLine.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyqtgraph/graphicsItems/InfiniteLine.py b/pyqtgraph/graphicsItems/InfiniteLine.py index 37d84c7e..cec6b967 100644 --- a/pyqtgraph/graphicsItems/InfiniteLine.py +++ b/pyqtgraph/graphicsItems/InfiniteLine.py @@ -222,8 +222,8 @@ class InfiniteLine(GraphicsObject): def setPos(self, pos): - if type(pos) in [list, tuple]: - newPos = pos + if type(pos) in [list, tuple, np.ndarray]: + newPos = list(pos) elif isinstance(pos, QtCore.QPointF): newPos = [pos.x(), pos.y()] else: From 421b7bdc788793c2f525688494acd9b296722db9 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Mon, 13 Jul 2020 12:52:25 -0700 Subject: [PATCH 04/12] add SignalProxy.block for temporarily disabling signal forwarding --- pyqtgraph/SignalProxy.py | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/pyqtgraph/SignalProxy.py b/pyqtgraph/SignalProxy.py index 46b44887..78dc09a9 100644 --- a/pyqtgraph/SignalProxy.py +++ b/pyqtgraph/SignalProxy.py @@ -3,6 +3,7 @@ from .Qt import QtCore from .ptime import time from . import ThreadsafeTimer import weakref +from .functions import SignalBlock __all__ = ['SignalProxy'] @@ -34,7 +35,7 @@ class SignalProxy(QtCore.QObject): self.args = None self.timer = ThreadsafeTimer.ThreadsafeTimer() self.timer.timeout.connect(self.flush) - self.block = False + self.blockSignal = False self.slot = weakref.ref(slot) self.lastFlushTime = None if slot is not None: @@ -45,7 +46,7 @@ class SignalProxy(QtCore.QObject): def signalReceived(self, *args): """Received signal. Cancel previous timer and store args to be forwarded later.""" - if self.block: + if self.blockSignal: return self.args = args if self.rateLimit == 0: @@ -61,11 +62,10 @@ class SignalProxy(QtCore.QObject): self.timer.stop() self.timer.start((min(leakTime, self.delay)*1000)+1) - def flush(self): """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.blockSignal: return False args, self.args = self.args, None self.timer.stop() @@ -75,7 +75,7 @@ class SignalProxy(QtCore.QObject): return True def disconnect(self): - self.block = True + self.blockSignal = True try: self.signal.disconnect(self.signalReceived) except: @@ -85,18 +85,10 @@ class SignalProxy(QtCore.QObject): except: pass - - -#def proxyConnect(source, signal, slot, delay=0.3): - #"""Connect a signal to a slot with delay. Returns the SignalProxy - #object that was created. Be sure to store this object so it is not - #garbage-collected immediately.""" - #sp = SignalProxy(source, signal, delay) - #if source is None: - #sp.connect(sp, QtCore.SIGNAL('signal'), slot) - #else: - #sp.connect(sp, signal, slot) - #return sp + def block(self): + """Return a SignalBlocker that temporarily blocks input signals to this proxy. + """ + return SignalBlock(self.signal, self.signalReceived) if __name__ == '__main__': From e2669f074b23ca120cf3e731e055b8141b5a5e44 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Mon, 13 Jul 2020 12:52:55 -0700 Subject: [PATCH 05/12] Add test for functions.subArray --- pyqtgraph/tests/test_functions.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pyqtgraph/tests/test_functions.py b/pyqtgraph/tests/test_functions.py index f9320ef2..e7849809 100644 --- a/pyqtgraph/tests/test_functions.py +++ b/pyqtgraph/tests/test_functions.py @@ -98,6 +98,19 @@ def check_interpolateArray(order): assert_array_almost_equal(r1, r2) +def test_subArray(): + a = np.array([0, 0, 111, 112, 113, 0, 121, 122, 123, 0, 0, 0, 211, 212, 213, 0, 221, 222, 223, 0, 0, 0, 0]) + b = pg.subArray(a, offset=2, shape=(2,2,3), stride=(10,4,1)) + c = np.array([[[111,112,113], [121,122,123]], [[211,212,213], [221,222,223]]]) + assert np.all(b == c) + + # operate over first axis; broadcast over the rest + aa = np.vstack([a, a/100.]).T + cc = np.empty(c.shape + (2,)) + cc[..., 0] = c + cc[..., 1] = c / 100. + bb = pg.subArray(aa, offset=2, shape=(2,2,3), stride=(10,4,1)) + assert np.all(bb == cc) def test_subArray(): a = np.array([0, 0, 111, 112, 113, 0, 121, 122, 123, 0, 0, 0, 211, 212, 213, 0, 221, 222, 223, 0, 0, 0, 0]) From 6214ff6de803c0c89e9eccebd1e41c58f76dc348 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Mon, 13 Jul 2020 12:53:06 -0700 Subject: [PATCH 06/12] Add Pa to units --- pyqtgraph/units.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyqtgraph/units.py b/pyqtgraph/units.py index 6b7f3099..adf5dc55 100644 --- a/pyqtgraph/units.py +++ b/pyqtgraph/units.py @@ -10,7 +10,7 @@ ## No unicode variable names (μ,Ω) allowed until python 3 SI_PREFIXES = 'yzafpnum kMGTPEZY' -UNITS = 'm,s,g,W,J,V,A,F,T,Hz,Ohm,S,N,C,px,b,B'.split(',') +UNITS = 'm,s,g,W,J,V,A,F,T,Hz,Ohm,S,N,C,px,b,B,Pa'.split(',') allUnits = {} def addUnit(p, n): From ab5a2c5d1167238c74eb862ae26a82c8d0ebc8d3 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Mon, 13 Jul 2020 12:53:59 -0700 Subject: [PATCH 07/12] BusyCursor: only restore cursor after all nested levels have exited --- pyqtgraph/widgets/BusyCursor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyqtgraph/widgets/BusyCursor.py b/pyqtgraph/widgets/BusyCursor.py index f6bbc84c..29fc3051 100644 --- a/pyqtgraph/widgets/BusyCursor.py +++ b/pyqtgraph/widgets/BusyCursor.py @@ -31,4 +31,5 @@ class BusyCursor(object): def __exit__(self, *args): if self._active: BusyCursor.active.pop(-1) - QtGui.QApplication.restoreOverrideCursor() + if len(BusyCursor.active) == 0: + QtGui.QApplication.restoreOverrideCursor() From f9327ea910bb09ce073d468f737002bcdc4155fe Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Mon, 13 Jul 2020 12:55:12 -0700 Subject: [PATCH 08/12] Rate-limit event processing inside ProgressDialog --- pyqtgraph/widgets/ProgressDialog.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pyqtgraph/widgets/ProgressDialog.py b/pyqtgraph/widgets/ProgressDialog.py index ae1826bb..7d2ef8a4 100644 --- a/pyqtgraph/widgets/ProgressDialog.py +++ b/pyqtgraph/widgets/ProgressDialog.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from ..Qt import QtGui, QtCore +from .. import ptime __all__ = ['ProgressDialog'] @@ -40,7 +41,7 @@ class ProgressDialog(QtGui.QProgressDialog): nested (bool) If True, then this progress bar will be displayed inside any pre-existing progress dialogs that also allow nesting. ============== ================================================================ - """ + """ # attributes used for nesting dialogs self.nestedLayout = None self._nestableWidgets = None @@ -48,6 +49,9 @@ class ProgressDialog(QtGui.QProgressDialog): self._topDialog = None self._subBars = [] self.nested = nested + + # for rate-limiting Qt event processing during progress bar update + self._lastProcessEvents = None isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() self.disabled = disable or (not isGuiThread) @@ -203,7 +207,10 @@ class ProgressDialog(QtGui.QProgressDialog): # Qt docs say this should happen automatically, but that doesn't seem # to be the case. if self.windowModality() == QtCore.Qt.WindowModal: - QtGui.QApplication.processEvents() + now = ptime.time() + if self._lastProcessEvents is None or (now - self._lastProcessEvents) > 0.2: + QtGui.QApplication.processEvents() + self._lastProcessEvents = now def setLabelText(self, val): if self.disabled: From eb4dd5626f5088f25874374585e99becf725f697 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Mon, 13 Jul 2020 13:05:56 -0700 Subject: [PATCH 09/12] SimpleParameter.setValue: coerce value for int parameters --- pyqtgraph/parametertree/parameterTypes.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index e8b55db7..e0df6be7 100644 --- a/pyqtgraph/parametertree/parameterTypes.py +++ b/pyqtgraph/parametertree/parameterTypes.py @@ -317,6 +317,11 @@ class SimpleParameter(Parameter): self.value = self.colorValue self.saveState = self.saveColorState + def setValue(self, value, blockSignal=None): + if self.opts['type'] == 'int': + value = int(value) + Parameter.setValue(self, value, blockSignal) + def colorValue(self): return fn.mkColor(Parameter.value(self)) From 78702eea8b01e9dba83bae70e223c07457760bd4 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Mon, 13 Jul 2020 13:06:27 -0700 Subject: [PATCH 10/12] code cleanup --- pyqtgraph/parametertree/parameterTypes.py | 37 ++++------------------- 1 file changed, 6 insertions(+), 31 deletions(-) diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index e0df6be7..5989229b 100644 --- a/pyqtgraph/parametertree/parameterTypes.py +++ b/pyqtgraph/parametertree/parameterTypes.py @@ -5,12 +5,12 @@ from .ParameterItem import ParameterItem from ..widgets.SpinBox import SpinBox from ..widgets.ColorButton import ColorButton from ..colormap import ColorMap -#from ..widgets.GradientWidget import GradientWidget ## creates import loop from .. import pixmaps as pixmaps from .. import functions as fn import os, sys from ..pgcollections import OrderedDict + class WidgetParameterItem(ParameterItem): """ ParameterTree item with: @@ -348,8 +348,7 @@ class SimpleParameter(Parameter): if not isinstance(v, ColorMap): raise TypeError("Cannot set colormap parameter from object %r" % v) return v - - + registerParameterType('int', SimpleParameter, override=True) registerParameterType('float', SimpleParameter, override=True) @@ -359,8 +358,6 @@ registerParameterType('color', SimpleParameter, override=True) registerParameterType('colormap', SimpleParameter, override=True) - - class GroupParameterItem(ParameterItem): """ Group parameters are used mainly as a generic parent item that holds (and groups!) a set @@ -463,7 +460,8 @@ class GroupParameterItem(ParameterItem): self.addWidget.addItem(t) finally: self.addWidget.blockSignals(False) - + + class GroupParameter(Parameter): """ Group parameters are used mainly as a generic parent item that holds (and groups!) a set @@ -491,14 +489,10 @@ class GroupParameter(Parameter): """Change the list of options available for the user to add to the group.""" self.setOpts(addList=vals) - registerParameterType('group', GroupParameter, override=True) - - - class ListParameterItem(WidgetParameterItem): """ WidgetParameterItem subclass providing comboBox that lets the user select from a list of options. @@ -508,7 +502,6 @@ class ListParameterItem(WidgetParameterItem): self.targetValue = None WidgetParameterItem.__init__(self, param, depth) - def makeWidget(self): opts = self.param.opts t = opts['type'] @@ -556,7 +549,6 @@ class ListParameterItem(WidgetParameterItem): self.updateDisplayLabel() finally: self.widget.blockSignals(False) - class ListParameter(Parameter): @@ -566,7 +558,7 @@ class ListParameter(Parameter): self.forward = OrderedDict() ## {name: value, ...} self.reverse = ([], []) ## ([value, ...], [name, ...]) - ## Parameter uses 'limits' option to define the set of allowed values + # Parameter uses 'limits' option to define the set of allowed values if 'values' in opts: opts['limits'] = opts['values'] if opts.get('limits', None) is None: @@ -581,24 +573,9 @@ class ListParameter(Parameter): if len(self.reverse[0]) > 0 and self.value() not in self.reverse[0]: self.setValue(self.reverse[0][0]) - #def addItem(self, name, value=None): - #if name in self.forward: - #raise Exception("Name '%s' is already in use for this parameter" % name) - #limits = self.opts['limits'] - #if isinstance(limits, dict): - #limits = limits.copy() - #limits[name] = value - #self.setLimits(limits) - #else: - #if value is not None: - #raise Exception ## raise exception or convert to dict? - #limits = limits[:] - #limits.append(name) - ## what if limits == None? - @staticmethod def mapping(limits): - ## Return forward and reverse mapping objects given a limit specification + # Return forward and reverse mapping objects given a limit specification forward = OrderedDict() ## {name: value, ...} reverse = ([], []) ## ([value, ...], [name, ...]) if isinstance(limits, dict): @@ -663,7 +640,6 @@ class ActionParameter(Parameter): registerParameterType('action', ActionParameter, override=True) - class TextParameterItem(WidgetParameterItem): def __init__(self, param, depth): WidgetParameterItem.__init__(self, param, depth) @@ -698,7 +674,6 @@ class TextParameterItem(WidgetParameterItem): class TextParameter(Parameter): """Editable string; displayed as large text box in the tree.""" itemClass = TextParameterItem - registerParameterType('text', TextParameter, override=True) From 0df7cbcd0695c2fc14cc5ae4a1dc41e0f2e53397 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Mon, 13 Jul 2020 13:10:58 -0700 Subject: [PATCH 11/12] Fix Vector.__init__ for Qt5 + cleanup --- pyqtgraph/Vector.py | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/pyqtgraph/Vector.py b/pyqtgraph/Vector.py index f2166c45..cbc98f5a 100644 --- a/pyqtgraph/Vector.py +++ b/pyqtgraph/Vector.py @@ -14,22 +14,33 @@ class Vector(QtGui.QVector3D): def __init__(self, *args): if len(args) == 1: if isinstance(args[0], QtCore.QSizeF): - QtGui.QVector3D.__init__(self, float(args[0].width()), float(args[0].height()), 0) - return + x = float(args[0].width()) + y = float(args[0].height()) + z = 0 elif isinstance(args[0], QtCore.QPoint) or isinstance(args[0], QtCore.QPointF): - QtGui.QVector3D.__init__(self, float(args[0].x()), float(args[0].y()), 0) + x = float(args[0].x()) + y = float(args[0].y()) + z = 0 + elif isinstance(args[0], QtGui.QVector3D): + x = args[0].x() + y = args[0].y() + z = args[0].z() elif hasattr(args[0], '__getitem__'): vals = list(args[0]) if len(vals) == 2: - vals.append(0) - if len(vals) != 3: - raise Exception('Cannot init Vector with sequence of length %d' % len(args[0])) - QtGui.QVector3D.__init__(self, *vals) - return + x, y = vals + z = 0 + elif len(vals) == 3: + x, y, z = vals + else: + raise ValueError('Cannot init Vector with sequence of length %d' % len(args[0])) elif len(args) == 2: - QtGui.QVector3D.__init__(self, args[0], args[1], 0) - return - QtGui.QVector3D.__init__(self, *args) + x, y = args + z = 0 + else: + x, y, z = args # Could raise ValueError + + QtGui.QVector3D.__init__(self, x, y, z) def __len__(self): return 3 From 605b0b2144eb2efa848bf66f80b502ecf8680263 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Mon, 13 Jul 2020 13:45:31 -0700 Subject: [PATCH 12/12] code cleanup --- pyqtgraph/parametertree/parameterTypes.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index 5989229b..a126793c 100644 --- a/pyqtgraph/parametertree/parameterTypes.py +++ b/pyqtgraph/parametertree/parameterTypes.py @@ -38,7 +38,6 @@ class WidgetParameterItem(ParameterItem): self.hideWidget = True ## hide edit widget, replace with label when not selected ## set this to False to keep the editor widget always visible - ## build widget into column 1 with a display label and default button. w = self.makeWidget() self.widget = w @@ -162,8 +161,6 @@ class WidgetParameterItem(ParameterItem): self.focusNext(forward=False) return True ## don't let anyone else see this event - #elif ev.type() == ev.FocusOut: - #self.hideEditor() return False def setFocus(self): @@ -272,7 +269,6 @@ class WidgetParameterItem(ParameterItem): def optsChanged(self, param, opts): """Called when any options are changed that are not name, value, default, or limits""" - #print "opts changed:", opts ParameterItem.optsChanged(self, param, opts) if 'readonly' in opts: @@ -349,7 +345,7 @@ class SimpleParameter(Parameter): raise TypeError("Cannot set colormap parameter from object %r" % v) return v - + registerParameterType('int', SimpleParameter, override=True) registerParameterType('float', SimpleParameter, override=True) registerParameterType('bool', SimpleParameter, override=True) @@ -385,7 +381,6 @@ class GroupParameterItem(ParameterItem): w.setLayout(l) l.addWidget(self.addWidget) l.addStretch() - #l.addItem(QtGui.QSpacerItem(200, 10, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)) self.addWidgetBox = w self.addItem = QtGui.QTreeWidgetItem([]) self.addItem.setFlags(QtCore.Qt.ItemIsEnabled)