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__': 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 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: 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") 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: diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index e8b55db7..a126793c 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: @@ -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: @@ -317,6 +313,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)) @@ -343,9 +344,8 @@ 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) registerParameterType('bool', SimpleParameter, override=True) @@ -354,8 +354,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 @@ -383,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) @@ -458,7 +455,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 @@ -486,14 +484,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. @@ -503,7 +497,6 @@ class ListParameterItem(WidgetParameterItem): self.targetValue = None WidgetParameterItem.__init__(self, param, depth) - def makeWidget(self): opts = self.param.opts t = opts['type'] @@ -551,7 +544,6 @@ class ListParameterItem(WidgetParameterItem): self.updateDisplayLabel() finally: self.widget.blockSignals(False) - class ListParameter(Parameter): @@ -561,7 +553,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: @@ -576,24 +568,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): @@ -658,7 +635,6 @@ class ActionParameter(Parameter): registerParameterType('action', ActionParameter, override=True) - class TextParameterItem(WidgetParameterItem): def __init__(self, param, depth): WidgetParameterItem.__init__(self, param, depth) @@ -693,7 +669,6 @@ class TextParameterItem(WidgetParameterItem): class TextParameter(Parameter): """Editable string; displayed as large text box in the tree.""" itemClass = TextParameterItem - registerParameterType('text', TextParameter, override=True) 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]) 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): 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() 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: