Ensure synchronization of parameter and widget's values
This commit is contained in:
parent
6313bb8ae3
commit
0703e3db9b
@ -64,7 +64,7 @@ class ScalableGroup(pTypes.GroupParameter):
|
||||
params = [
|
||||
{'name': 'Basic parameter data types', 'type': 'group', 'children': [
|
||||
{'name': 'Integer', 'type': 'int', 'value': 10},
|
||||
{'name': 'Float', 'type': 'float', 'value': 10.5, 'step': 0.1},
|
||||
{'name': 'Float', 'type': 'float', 'value': 10.5, 'step': 0.1, 'finite': False},
|
||||
{'name': 'String', 'type': 'str', 'value': "hi"},
|
||||
{'name': 'List', 'type': 'list', 'values': [1,2,3], 'value': 2},
|
||||
{'name': 'Named List', 'type': 'list', 'values': {"one": 1, "two": "twosies", "three": [3,3,3]}, 'value': 2},
|
||||
|
@ -1,7 +1,7 @@
|
||||
import numpy as np
|
||||
from .Qt import QtGui, QtCore
|
||||
from .python2_3 import basestring
|
||||
from .functions import mkColor
|
||||
from .functions import mkColor, eq
|
||||
|
||||
|
||||
class ColorMap(object):
|
||||
@ -257,3 +257,8 @@ class ColorMap(object):
|
||||
pos = repr(self.pos).replace('\n', '')
|
||||
color = repr(self.color).replace('\n', '')
|
||||
return "ColorMap(%s, %s)" % (pos, color)
|
||||
|
||||
def __eq__(self, other):
|
||||
if other is None:
|
||||
return False
|
||||
return eq(self.pos, other.pos) and eq(self.color, other.color)
|
||||
|
@ -420,18 +420,25 @@ def eq(a, b):
|
||||
|
||||
This function has some important differences from the == operator:
|
||||
|
||||
1. Returns True if a IS b, even if a==b still evaluates to False, such as with nan values.
|
||||
2. Tests for equivalence using ==, but silently ignores some common exceptions that can occur
|
||||
1. Returns True if a IS b, even if a==b still evaluates to False.
|
||||
2. While a is b will catch the case with np.nan values, special handling is done for distinct
|
||||
float('nan') instances using np.isnan.
|
||||
3. Tests for equivalence using ==, but silently ignores some common exceptions that can occur
|
||||
(AtrtibuteError, ValueError).
|
||||
3. When comparing arrays, returns False if the array shapes are not the same.
|
||||
4. When comparing arrays of the same shape, returns True only if all elements are equal (whereas
|
||||
4. When comparing arrays, returns False if the array shapes are not the same.
|
||||
5. When comparing arrays of the same shape, returns True only if all elements are equal (whereas
|
||||
the == operator would return a boolean array).
|
||||
5. Collections (dict, list, etc.) must have the same type to be considered equal. One
|
||||
consequence is that comparing a dict to an OrderedDict will always return False.
|
||||
6. Collections (dict, list, etc.) must have the same type to be considered equal. One
|
||||
consequence is that comparing a dict to an OrderedDict will always return False.
|
||||
"""
|
||||
if a is b:
|
||||
return True
|
||||
|
||||
# The above catches np.nan, but not float('nan')
|
||||
if isinstance(a, float) and isinstance(b, float):
|
||||
if np.isnan(a) and np.isnan(b):
|
||||
return True
|
||||
|
||||
# Avoid comparing large arrays against scalars; this is expensive and we know it should return False.
|
||||
aIsArr = isinstance(a, (np.ndarray, MetaArray))
|
||||
bIsArr = isinstance(b, (np.ndarray, MetaArray))
|
||||
|
@ -1,4 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from .. import functions as fn
|
||||
from ..Qt import QtGui, QtCore
|
||||
import os, weakref, re
|
||||
from ..pgcollections import OrderedDict
|
||||
@ -283,15 +284,15 @@ class Parameter(QtCore.QObject):
|
||||
if blockSignal is not None:
|
||||
self.sigValueChanged.disconnect(blockSignal)
|
||||
value = self._interpretValue(value)
|
||||
if self.opts['value'] == value:
|
||||
if fn.eq(self.opts['value'], value):
|
||||
return value
|
||||
self.opts['value'] = value
|
||||
self.sigValueChanged.emit(self, value)
|
||||
self.sigValueChanged.emit(self, value) # value might change after signal is received by tree item
|
||||
finally:
|
||||
if blockSignal is not None:
|
||||
self.sigValueChanged.connect(blockSignal)
|
||||
|
||||
return value
|
||||
return self.opts['value']
|
||||
|
||||
def _interpretValue(self, v):
|
||||
return v
|
||||
|
@ -216,13 +216,16 @@ class WidgetParameterItem(ParameterItem):
|
||||
def valueChanged(self, param, val, force=False):
|
||||
## called when the parameter's value has changed
|
||||
ParameterItem.valueChanged(self, param, val)
|
||||
self.widget.sigChanged.disconnect(self.widgetValueChanged)
|
||||
try:
|
||||
if force or val != self.widget.value():
|
||||
if force or not fn.eq(val, self.widget.value()):
|
||||
try:
|
||||
self.widget.sigChanged.disconnect(self.widgetValueChanged)
|
||||
self.param.sigValueChanged.disconnect(self.valueChanged)
|
||||
self.widget.setValue(val)
|
||||
self.updateDisplayLabel(val) ## always make sure label is updated, even if values match!
|
||||
finally:
|
||||
self.widget.sigChanged.connect(self.widgetValueChanged)
|
||||
self.param.setValue(self.widget.value())
|
||||
finally:
|
||||
self.widget.sigChanged.connect(self.widgetValueChanged)
|
||||
self.param.sigValueChanged.connect(self.valueChanged)
|
||||
self.updateDisplayLabel() ## always make sure label is updated, even if values match!
|
||||
self.updateDefaultBtn()
|
||||
|
||||
def updateDefaultBtn(self):
|
||||
|
@ -127,8 +127,31 @@ def check_param_types(param, types, map_func, init, objs, keys):
|
||||
raise Exception("Setting %s parameter value to %r should have raised an exception." % (param, v))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def test_limits_enforcement():
|
||||
p = pt.Parameter.create(name='params', type='group', children=[
|
||||
dict(name='float', type='float', limits=[0, 1]),
|
||||
dict(name='int', type='int', bounds=[0, 1]),
|
||||
dict(name='list', type='list', values=['x', 'y']),
|
||||
dict(name='dict', type='list', values={'x': 1, 'y': 2}),
|
||||
])
|
||||
t = pt.ParameterTree()
|
||||
t.setParameters(p)
|
||||
for k, vin, vout in [('float', -1, 0),
|
||||
('float', 2, 1),
|
||||
('int', -1, 0),
|
||||
('int', 2, 1),
|
||||
('list', 'w', 'x'),
|
||||
('dict', 'w', 1)]:
|
||||
p[k] = vin
|
||||
assert p[k] == vout
|
||||
|
||||
|
||||
def test_data_race():
|
||||
p = pt.Parameter.create(name='int', type='int', value=0)
|
||||
|
||||
p.sigValueChanged.connect(lambda: p.setValue(1)) # connect signal before adding to tree to ensure priority
|
||||
t = pt.ParameterTree()
|
||||
t.setParameters(p)
|
||||
pi = t.nextFocusableChild(t.invisibleRootItem())
|
||||
pi.widget.setValue(2)
|
||||
assert p.value() == pi.widget.value() == 1
|
||||
|
@ -360,10 +360,9 @@ class SpinBox(QtGui.QAbstractSpinBox):
|
||||
if not isinstance(value, D):
|
||||
value = D(asUnicode(value))
|
||||
|
||||
changed = value != self.val
|
||||
prev = self.val
|
||||
|
||||
self.val = value
|
||||
prev, self.val = self.val, value
|
||||
changed = not fn.eq(value, prev) # use fn.eq to handle nan
|
||||
|
||||
if update and (changed or not bounded):
|
||||
self.updateText(prev=prev)
|
||||
|
||||
@ -381,7 +380,7 @@ class SpinBox(QtGui.QAbstractSpinBox):
|
||||
|
||||
def delayedChange(self):
|
||||
try:
|
||||
if self.val != self.lastValEmitted:
|
||||
if not fn.eq(self.val, self.lastValEmitted): # use fn.eq to handle nan
|
||||
self.emitChanged()
|
||||
except RuntimeError:
|
||||
pass ## This can happen if we try to handle a delayed signal after someone else has already deleted the underlying C++ object.
|
||||
|
Loading…
x
Reference in New Issue
Block a user