Better parameter tree testing (#1953)
* Allows values to be numpy arrays * Bugfix: Slider now works when limits didn't change during `optsChanged` * Improved testing + layout of param tree example * Also fix numpy-like values in list `setValue` * use proper hex formatting for value * Fix code warnings * Avoids use of configfile in parametertree * Avoid shadowing variable names * Add explanatory comment to `makeAllParamTypes` * Allow string options to be 'unset' in file, etc. parameters example * Bugfix: unintunitive option unsetting for file window title * don't use lambda in signal connect * Remove unused import
This commit is contained in:
parent
e9d3b6ddd2
commit
8d3e6cbd22
|
@ -0,0 +1,93 @@
|
||||||
|
from pyqtgraph.parametertree import Parameter
|
||||||
|
from pyqtgraph.parametertree.Parameter import PARAM_TYPES
|
||||||
|
from pyqtgraph.parametertree.parameterTypes import GroupParameter
|
||||||
|
from ._paramtreecfg import cfg
|
||||||
|
|
||||||
|
_encounteredTypes = {'group'}
|
||||||
|
|
||||||
|
def makeChild(chType, cfgDict):
|
||||||
|
_encounteredTypes.add(chType)
|
||||||
|
param = Parameter.create(name='widget', type=chType)
|
||||||
|
param.setDefault(param.value())
|
||||||
|
|
||||||
|
def setOpt(_param, _val):
|
||||||
|
# Treat blank strings as "None" to allow 'unsetting' that option
|
||||||
|
if isinstance(_val, str) and _val == '':
|
||||||
|
_val = None
|
||||||
|
param.setOpts(**{_param.name(): _val})
|
||||||
|
|
||||||
|
optsChildren = []
|
||||||
|
metaChildren = []
|
||||||
|
for optName, optVals in cfgDict.items():
|
||||||
|
child = Parameter.create(name=optName, **optVals)
|
||||||
|
if ' ' in optName:
|
||||||
|
metaChildren.append(child)
|
||||||
|
else:
|
||||||
|
optsChildren.append(child)
|
||||||
|
child.sigValueChanged.connect(setOpt)
|
||||||
|
# Poplate initial options
|
||||||
|
for p in optsChildren:
|
||||||
|
setOpt(p, p.value())
|
||||||
|
|
||||||
|
grp = Parameter.create(name=f'Sample {chType.title()}', type='group', children=metaChildren + [param] + optsChildren)
|
||||||
|
grp.setOpts(expanded=False)
|
||||||
|
return grp
|
||||||
|
|
||||||
|
def makeMetaChild(name, cfgDict):
|
||||||
|
children = []
|
||||||
|
for chName, chOpts in cfgDict.items():
|
||||||
|
if not isinstance(chOpts, dict):
|
||||||
|
ch = Parameter.create(name=chName, type=chName, value=chOpts)
|
||||||
|
else:
|
||||||
|
ch = Parameter.create(name=chName, **chOpts)
|
||||||
|
_encounteredTypes.add(ch.type())
|
||||||
|
children.append(ch)
|
||||||
|
param = Parameter.create(name=name, type='group', children=children)
|
||||||
|
param.setOpts(expanded=False)
|
||||||
|
return param
|
||||||
|
|
||||||
|
def makeAllParamTypes():
|
||||||
|
children = []
|
||||||
|
for name, paramCfg in cfg.items():
|
||||||
|
if ' ' in name:
|
||||||
|
children.append(makeMetaChild(name, paramCfg))
|
||||||
|
else:
|
||||||
|
children.append(makeChild(name, paramCfg))
|
||||||
|
|
||||||
|
params = Parameter.create(name='Example Parameters', type='group', children=children)
|
||||||
|
|
||||||
|
# Slider needs minor tweak
|
||||||
|
sliderGrp = params.child('Sample Slider')
|
||||||
|
slider = sliderGrp.child('widget')
|
||||||
|
slider.setOpts(limits=[0, 100])
|
||||||
|
|
||||||
|
# Also minor tweak to meta opts
|
||||||
|
def setOpt(_param, _val):
|
||||||
|
infoChild.setOpts(**{_param.name(): _val})
|
||||||
|
meta = params.child('Applies to All Types')
|
||||||
|
infoChild = meta.child('Extra Information')
|
||||||
|
for child in meta.children()[1:]:
|
||||||
|
child.sigValueChanged.connect(setOpt)
|
||||||
|
|
||||||
|
def onChange(_param, _val):
|
||||||
|
if _val == 'Use span':
|
||||||
|
span = slider.opts.pop('span', None)
|
||||||
|
slider.setOpts(span=span)
|
||||||
|
else:
|
||||||
|
limits = slider.opts.pop('limits', None)
|
||||||
|
slider.setOpts(limits=limits)
|
||||||
|
sliderGrp.child('How to Set').sigValueChanged.connect(onChange)
|
||||||
|
|
||||||
|
def activate(action):
|
||||||
|
for ch in params:
|
||||||
|
if isinstance(ch, GroupParameter):
|
||||||
|
ch.setOpts(expanded=action.name() == 'Expand All')
|
||||||
|
|
||||||
|
for name in 'Collapse', 'Expand':
|
||||||
|
btn = Parameter.create(name=f'{name} All', type='action')
|
||||||
|
btn.sigActivated.connect(activate)
|
||||||
|
params.insertChild(0, btn)
|
||||||
|
missing = set(PARAM_TYPES).difference(_encounteredTypes)
|
||||||
|
if missing:
|
||||||
|
raise RuntimeError(f'{missing} parameters are not represented')
|
||||||
|
return params
|
|
@ -0,0 +1,187 @@
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from pyqtgraph.Qt import QtWidgets
|
||||||
|
from pyqtgraph.parametertree.parameterTypes import QtEnumParameter as enum
|
||||||
|
|
||||||
|
dlg = QtWidgets.QFileDialog
|
||||||
|
|
||||||
|
cfg = {
|
||||||
|
'list': {
|
||||||
|
'limits': {
|
||||||
|
'type': 'checklist',
|
||||||
|
'limits': ['a', 'b', 'c']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'file': {
|
||||||
|
'acceptMode': {
|
||||||
|
'type': 'list',
|
||||||
|
'limits': list(enum(dlg.AcceptMode, dlg).enumMap)
|
||||||
|
},
|
||||||
|
'fileMode': {
|
||||||
|
'type': 'list',
|
||||||
|
'limits': list(enum(dlg.FileMode, dlg).enumMap)
|
||||||
|
},
|
||||||
|
'viewMode': {
|
||||||
|
'type': 'list',
|
||||||
|
'limits': list(enum(dlg.ViewMode, dlg).enumMap)
|
||||||
|
},
|
||||||
|
'dialogLabel': {
|
||||||
|
'type': 'list',
|
||||||
|
'limits': list(enum(dlg.DialogLabel, dlg).enumMap)
|
||||||
|
},
|
||||||
|
'relativeTo': {
|
||||||
|
'type': 'str',
|
||||||
|
'value': None
|
||||||
|
},
|
||||||
|
'directory': {
|
||||||
|
'type': 'str',
|
||||||
|
'value': None
|
||||||
|
},
|
||||||
|
'windowTitle': {
|
||||||
|
'type': 'str',
|
||||||
|
'value': None
|
||||||
|
},
|
||||||
|
'nameFilter': {
|
||||||
|
'type': 'str',
|
||||||
|
'value': None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'float': {
|
||||||
|
'Float Information': {
|
||||||
|
'type': 'str',
|
||||||
|
'readonly': True,
|
||||||
|
'value': 'Note that all options except "finite" also apply to "int" parameters',
|
||||||
|
},
|
||||||
|
'step': {
|
||||||
|
'type': 'float',
|
||||||
|
'limits': [0, None],
|
||||||
|
'value': 1,
|
||||||
|
},
|
||||||
|
'limits': {
|
||||||
|
'type': 'list',
|
||||||
|
'limits': {'[0, None]': [0, None], '[1, 5]': [1, 5]},
|
||||||
|
},
|
||||||
|
'suffix': {
|
||||||
|
'type': 'list',
|
||||||
|
'limits': ['Hz', 's', 'm'],
|
||||||
|
},
|
||||||
|
'siPrefix': {
|
||||||
|
'type': 'bool',
|
||||||
|
'value': True
|
||||||
|
},
|
||||||
|
'finite': {
|
||||||
|
'type': 'bool',
|
||||||
|
'value': True,
|
||||||
|
},
|
||||||
|
'dec': {
|
||||||
|
'type': 'bool',
|
||||||
|
'value': False,
|
||||||
|
},
|
||||||
|
'minStep': {
|
||||||
|
'type': 'float',
|
||||||
|
'value': 1.0e-12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
'checklist': {
|
||||||
|
'limits': {
|
||||||
|
'type': 'checklist',
|
||||||
|
'limits': ['one', 'two', 'three', 'four'],
|
||||||
|
},
|
||||||
|
'exclusive': {
|
||||||
|
'type': 'bool',
|
||||||
|
'value': False,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'pen': {
|
||||||
|
'Pen Information': {
|
||||||
|
'type': 'str',
|
||||||
|
'value': 'Click the button to see options',
|
||||||
|
'readonly': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
'slider': {
|
||||||
|
'step': {
|
||||||
|
'type': 'float',
|
||||||
|
'limits': [0, None],
|
||||||
|
'value': 1, },
|
||||||
|
'format': {
|
||||||
|
'type': 'str',
|
||||||
|
'value': '{0:>3}',
|
||||||
|
},
|
||||||
|
'precision': {
|
||||||
|
'type': 'int',
|
||||||
|
'value': 2,
|
||||||
|
'limits': [1, None],
|
||||||
|
},
|
||||||
|
'span': {
|
||||||
|
'type': 'list',
|
||||||
|
'limits': {'linspace(-pi, pi)': np.linspace(-np.pi, np.pi), 'arange(10)**2': np.arange(10) ** 2},
|
||||||
|
},
|
||||||
|
|
||||||
|
'How to Set': {
|
||||||
|
'type': 'list',
|
||||||
|
'limits': ['Use span', 'Use step + limits'],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'calendar': {
|
||||||
|
'format': {
|
||||||
|
'type': 'str',
|
||||||
|
'value': 'MM DD',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'Applies to All Types': {
|
||||||
|
'Extra Information': {
|
||||||
|
'type': 'text',
|
||||||
|
'value': 'These apply to all parameters. Watch how this text box is altered by any setting you change.',
|
||||||
|
'default': 'These apply to all parameters. Watch how this text box is altered by any setting you change.',
|
||||||
|
'readonly': True,
|
||||||
|
},
|
||||||
|
'readonly': {
|
||||||
|
'type': 'bool',
|
||||||
|
'value': True,
|
||||||
|
},
|
||||||
|
'removable': {
|
||||||
|
'type': 'bool',
|
||||||
|
'tip': 'Adds a context menu option to remove this parameter',
|
||||||
|
'value': False,
|
||||||
|
},
|
||||||
|
'visible': {
|
||||||
|
'type': 'bool',
|
||||||
|
'value': True,
|
||||||
|
},
|
||||||
|
'disabled': {
|
||||||
|
'type': 'bool',
|
||||||
|
'value': False,
|
||||||
|
},
|
||||||
|
'title': {
|
||||||
|
'type': 'str',
|
||||||
|
'value': 'Meta Options',
|
||||||
|
},
|
||||||
|
'default': {
|
||||||
|
'tip': 'The default value that gets set when clicking the arrow in the right column',
|
||||||
|
'type': 'str',
|
||||||
|
},
|
||||||
|
'expanded': {
|
||||||
|
'type': 'bool',
|
||||||
|
'value': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
'No Extra Options': {
|
||||||
|
'text': 'Unlike the other parameters shown, these don\'t have extra settable options.\n' \
|
||||||
|
+ 'Note: "int" *does* have the same options as float, mentioned above',
|
||||||
|
'int': 10,
|
||||||
|
'str': 'Hi, world!',
|
||||||
|
'color': '#fff',
|
||||||
|
'bool': False,
|
||||||
|
'colormap': None,
|
||||||
|
'progress': 50,
|
||||||
|
'action': None,
|
||||||
|
'font': 'Inter',
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
This example demonstrates the use of pyqtgraph's parametertree system. This provides
|
This example demonstrates the use of pyqtgraph's parametertree system. This provides
|
||||||
a simple way to generate user interfaces that control sets of parameters. The example
|
a simple way to generate user interfaces that control sets of parameters. The example
|
||||||
|
@ -6,17 +5,20 @@ demonstrates a variety of different parameter types (int, float, list, etc.)
|
||||||
as well as some customized parameter types
|
as well as some customized parameter types
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import os
|
|
||||||
|
|
||||||
import initExample ## Add path to library (just for examples; you do not need this)
|
import initExample ## Add path to library (just for examples; you do not need this)
|
||||||
|
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
from pyqtgraph.Qt import QtCore, QtGui
|
# `makeAllParamTypes` creates several parameters from a dictionary of config specs.
|
||||||
|
# This contains information about the options for each parameter so they can be directly
|
||||||
|
# inserted into the example parameter tree. To create your own parameters, simply follow
|
||||||
|
# the guidelines demonstrated by other parameters created here.
|
||||||
|
from examples._buildParamTypes import makeAllParamTypes
|
||||||
|
from pyqtgraph.Qt import QtGui
|
||||||
|
|
||||||
|
|
||||||
app = pg.mkQApp("Parameter Tree Example")
|
app = pg.mkQApp("Parameter Tree Example")
|
||||||
import pyqtgraph.parametertree.parameterTypes as pTypes
|
import pyqtgraph.parametertree.parameterTypes as pTypes
|
||||||
from pyqtgraph.parametertree import Parameter, ParameterTree, ParameterItem, registerParameterType
|
from pyqtgraph.parametertree import Parameter, ParameterTree
|
||||||
|
|
||||||
|
|
||||||
## test subclassing parameters
|
## test subclassing parameters
|
||||||
|
@ -62,39 +64,7 @@ class ScalableGroup(pTypes.GroupParameter):
|
||||||
|
|
||||||
|
|
||||||
params = [
|
params = [
|
||||||
{'name': 'Basic parameter data types', 'type': 'group', 'children': [
|
makeAllParamTypes(),
|
||||||
{'name': 'Integer', 'type': 'int', 'value': 10},
|
|
||||||
{'name': 'Float', 'type': 'float', 'value': 10.5, 'step': 0.1, 'finite': False},
|
|
||||||
{'name': 'String', 'type': 'str', 'value': "hi", 'tip': 'Well hello'},
|
|
||||||
{'name': 'Checklist', 'type': 'checklist', 'limits': [1,2,3], 'value': 2},
|
|
||||||
{'name': 'List', 'type': 'list', 'limits': [1,2,3], 'value': 2},
|
|
||||||
{'name': 'Named List', 'type': 'list', 'limits': {"one": 1, "two": "twosies", "three": [3,3,3]}, 'value': 2},
|
|
||||||
{'name': 'Boolean', 'type': 'bool', 'value': True, 'tip': "This is a checkbox"},
|
|
||||||
{'name': 'Color', 'type': 'color', 'value': "#FF0", 'tip': "This is a color button"},
|
|
||||||
{'name': 'Gradient', 'type': 'colormap'},
|
|
||||||
{'name': 'Subgroup', 'type': 'group', 'children': [
|
|
||||||
{'name': 'Sub-param 1', 'type': 'int', 'value': 10},
|
|
||||||
{'name': 'Sub-param 2', 'type': 'float', 'value': 1.2e6},
|
|
||||||
]},
|
|
||||||
{'name': 'Text Parameter', 'type': 'text', 'value': 'Some text...'},
|
|
||||||
{'name': 'Action Parameter', 'type': 'action', 'tip': 'Click me'},
|
|
||||||
]},
|
|
||||||
{'name': 'Custom Parameter Options', 'type': 'group', 'children': [
|
|
||||||
{'name': 'Pen', 'type': 'pen', 'value': pg.mkPen(color=(255,0,0), width=2)},
|
|
||||||
{'name': 'Progress bar', 'type': 'progress', 'value':50, 'limits':(0,100)},
|
|
||||||
{'name': 'Slider', 'type': 'slider', 'value':50, 'limits':(0,100)},
|
|
||||||
{'name': 'Font', 'type': 'font', 'value':QtGui.QFont("Inter")},
|
|
||||||
{'name': 'Calendar', 'type': 'calendar', 'value':QtCore.QDate.currentDate().addMonths(1)},
|
|
||||||
{'name': 'Open python file', 'type': 'file', 'fileMode': 'ExistingFile', 'nameFilter': 'Python file (*.py);;',
|
|
||||||
'value': 'parametertree.py', 'relativeTo': os.getcwd(), 'options': ['DontResolveSymlinks']}
|
|
||||||
]},
|
|
||||||
{'name': 'Numerical Parameter Options', 'type': 'group', 'children': [
|
|
||||||
{'name': 'Units + SI prefix', 'type': 'float', 'value': 1.2e-6, 'step': 1e-6, 'siPrefix': True, 'suffix': 'V'},
|
|
||||||
{'name': 'Limits (min=7;max=15)', 'type': 'int', 'value': 11, 'limits': (7, 15), 'default': -6},
|
|
||||||
{'name': 'Int suffix', 'type': 'int', 'value': 9, 'suffix': 'V'},
|
|
||||||
{'name': 'DEC stepping', 'type': 'float', 'value': 1.2e6, 'dec': True, 'step': 1, 'minStep': 1.0e-12, 'siPrefix': True, 'suffix': 'Hz'},
|
|
||||||
|
|
||||||
]},
|
|
||||||
{'name': 'Save/Restore functionality', 'type': 'group', 'children': [
|
{'name': 'Save/Restore functionality', 'type': 'group', 'children': [
|
||||||
{'name': 'Save State', 'type': 'action'},
|
{'name': 'Save State', 'type': 'action'},
|
||||||
{'name': 'Restore State', 'type': 'action', 'children': [
|
{'name': 'Restore State', 'type': 'action', 'children': [
|
||||||
|
@ -102,12 +72,6 @@ params = [
|
||||||
{'name': 'Remove extra items', 'type': 'bool', 'value': True},
|
{'name': 'Remove extra items', 'type': 'bool', 'value': True},
|
||||||
]},
|
]},
|
||||||
]},
|
]},
|
||||||
{'name': 'Extra Parameter Options', 'type': 'group', 'children': [
|
|
||||||
{'name': 'Read-only', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'readonly': True},
|
|
||||||
{'name': 'Disabled', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'enabled': False},
|
|
||||||
{'name': 'Renamable', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'renamable': True},
|
|
||||||
{'name': 'Removable', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'removable': True},
|
|
||||||
]},
|
|
||||||
{'name': 'Custom context menu', 'type': 'group', 'children': [
|
{'name': 'Custom context menu', 'type': 'group', 'children': [
|
||||||
{'name': 'List contextMenu', 'type': 'float', 'value': 0, 'context': [
|
{'name': 'List contextMenu', 'type': 'float', 'value': 0, 'context': [
|
||||||
'menu1',
|
'menu1',
|
||||||
|
|
|
@ -43,7 +43,7 @@ def writeConfigFile(data, fname):
|
||||||
fd.write(s)
|
fd.write(s)
|
||||||
|
|
||||||
|
|
||||||
def readConfigFile(fname):
|
def readConfigFile(fname, **scope):
|
||||||
#cwd = os.getcwd()
|
#cwd = os.getcwd()
|
||||||
global GLOBAL_PATH
|
global GLOBAL_PATH
|
||||||
if GLOBAL_PATH is not None:
|
if GLOBAL_PATH is not None:
|
||||||
|
@ -52,6 +52,21 @@ def readConfigFile(fname):
|
||||||
fname = fname2
|
fname = fname2
|
||||||
|
|
||||||
GLOBAL_PATH = os.path.dirname(os.path.abspath(fname))
|
GLOBAL_PATH = os.path.dirname(os.path.abspath(fname))
|
||||||
|
|
||||||
|
local = {**scope, **units.allUnits}
|
||||||
|
local['OrderedDict'] = OrderedDict
|
||||||
|
local['readConfigFile'] = readConfigFile
|
||||||
|
local['Point'] = Point
|
||||||
|
local['QtCore'] = QtCore
|
||||||
|
local['ColorMap'] = ColorMap
|
||||||
|
local['datetime'] = datetime
|
||||||
|
# Needed for reconstructing numpy arrays
|
||||||
|
local['array'] = numpy.array
|
||||||
|
for dtype in ['int8', 'uint8',
|
||||||
|
'int16', 'uint16', 'float16',
|
||||||
|
'int32', 'uint32', 'float32',
|
||||||
|
'int64', 'uint64', 'float64']:
|
||||||
|
local[dtype] = getattr(numpy, dtype)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
#os.chdir(newDir) ## bad.
|
#os.chdir(newDir) ## bad.
|
||||||
|
@ -59,7 +74,7 @@ def readConfigFile(fname):
|
||||||
s = fd.read()
|
s = fd.read()
|
||||||
s = s.replace("\r\n", "\n")
|
s = s.replace("\r\n", "\n")
|
||||||
s = s.replace("\r", "\n")
|
s = s.replace("\r", "\n")
|
||||||
data = parseString(s)[1]
|
data = parseString(s, **local)[1]
|
||||||
except ParseError:
|
except ParseError:
|
||||||
sys.exc_info()[1].fileName = fname
|
sys.exc_info()[1].fileName = fname
|
||||||
raise
|
raise
|
||||||
|
@ -93,7 +108,7 @@ def genString(data, indent=''):
|
||||||
s += indent + sk + ': ' + repr(data[k]).replace("\n", "\\\n") + '\n'
|
s += indent + sk + ': ' + repr(data[k]).replace("\n", "\\\n") + '\n'
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def parseString(lines, start=0):
|
def parseString(lines, start=0, **scope):
|
||||||
|
|
||||||
data = OrderedDict()
|
data = OrderedDict()
|
||||||
if isinstance(lines, str):
|
if isinstance(lines, str):
|
||||||
|
@ -135,33 +150,19 @@ def parseString(lines, start=0):
|
||||||
v = v.strip()
|
v = v.strip()
|
||||||
|
|
||||||
## set up local variables to use for eval
|
## set up local variables to use for eval
|
||||||
local = units.allUnits.copy()
|
|
||||||
local['OrderedDict'] = OrderedDict
|
|
||||||
local['readConfigFile'] = readConfigFile
|
|
||||||
local['Point'] = Point
|
|
||||||
local['QtCore'] = QtCore
|
|
||||||
local['ColorMap'] = ColorMap
|
|
||||||
local['datetime'] = datetime
|
|
||||||
# Needed for reconstructing numpy arrays
|
|
||||||
local['array'] = numpy.array
|
|
||||||
for dtype in ['int8', 'uint8',
|
|
||||||
'int16', 'uint16', 'float16',
|
|
||||||
'int32', 'uint32', 'float32',
|
|
||||||
'int64', 'uint64', 'float64']:
|
|
||||||
local[dtype] = getattr(numpy, dtype)
|
|
||||||
|
|
||||||
if len(k) < 1:
|
if len(k) < 1:
|
||||||
raise ParseError('Missing name preceding colon', ln+1, l)
|
raise ParseError('Missing name preceding colon', ln+1, l)
|
||||||
if k[0] == '(' and k[-1] == ')': ## If the key looks like a tuple, try evaluating it.
|
if k[0] == '(' and k[-1] == ')': ## If the key looks like a tuple, try evaluating it.
|
||||||
try:
|
try:
|
||||||
k1 = eval(k, local)
|
k1 = eval(k, scope)
|
||||||
if type(k1) is tuple:
|
if type(k1) is tuple:
|
||||||
k = k1
|
k = k1
|
||||||
except:
|
except:
|
||||||
|
# If tuple conversion fails, keep the string
|
||||||
pass
|
pass
|
||||||
if re.search(r'\S', v) and v[0] != '#': ## eval the value
|
if re.search(r'\S', v) and v[0] != '#': ## eval the value
|
||||||
try:
|
try:
|
||||||
val = eval(v, local)
|
val = eval(v, scope)
|
||||||
except:
|
except:
|
||||||
ex = sys.exc_info()[1]
|
ex = sys.exc_info()[1]
|
||||||
raise ParseError("Error evaluating expression '%s': [%s: %s]" % (v, ex.__class__.__name__, str(ex)), (ln+1), l)
|
raise ParseError("Error evaluating expression '%s': [%s: %s]" % (v, ex.__class__.__name__, str(ex)), (ln+1), l)
|
||||||
|
@ -171,7 +172,7 @@ def parseString(lines, start=0):
|
||||||
val = {}
|
val = {}
|
||||||
else:
|
else:
|
||||||
#print "Going deeper..", ln+1
|
#print "Going deeper..", ln+1
|
||||||
(ln, val) = parseString(lines, start=ln+1)
|
(ln, val) = parseString(lines, start=ln+1, **scope)
|
||||||
data[k] = val
|
data[k] = val
|
||||||
#print k, repr(val)
|
#print k, repr(val)
|
||||||
except ParseError:
|
except ParseError:
|
||||||
|
|
|
@ -516,7 +516,7 @@ class Parameter(QtCore.QObject):
|
||||||
self.setLimits(opts[k])
|
self.setLimits(opts[k])
|
||||||
elif k == 'default':
|
elif k == 'default':
|
||||||
self.setDefault(opts[k])
|
self.setDefault(opts[k])
|
||||||
elif k not in self.opts or self.opts[k] != opts[k]:
|
elif k not in self.opts or not fn.eq(self.opts[k], opts[k]):
|
||||||
self.opts[k] = opts[k]
|
self.opts[k] = opts[k]
|
||||||
changed[k] = opts[k]
|
changed[k] = opts[k]
|
||||||
|
|
||||||
|
|
|
@ -145,7 +145,8 @@ class FileParameterItem(StrParameterItem):
|
||||||
startDir = os.path.dirname(startDir)
|
startDir = os.path.dirname(startDir)
|
||||||
if os.path.exists(startDir):
|
if os.path.exists(startDir):
|
||||||
opts['directory'] = startDir
|
opts['directory'] = startDir
|
||||||
opts.setdefault('windowTitle', self.param.title())
|
if opts.get('windowTitle') is None:
|
||||||
|
opts['windowTitle'] = self.param.title()
|
||||||
|
|
||||||
fname = popupFilePicker(None, **opts)
|
fname = popupFilePicker(None, **opts)
|
||||||
if not fname:
|
if not fname:
|
||||||
|
|
|
@ -4,6 +4,7 @@ from collections import OrderedDict
|
||||||
from .basetypes import WidgetParameterItem
|
from .basetypes import WidgetParameterItem
|
||||||
from .. import Parameter
|
from .. import Parameter
|
||||||
from ...Qt import QtWidgets
|
from ...Qt import QtWidgets
|
||||||
|
from ... import functions as fn
|
||||||
|
|
||||||
|
|
||||||
class ListParameterItem(WidgetParameterItem):
|
class ListParameterItem(WidgetParameterItem):
|
||||||
|
@ -34,10 +35,12 @@ class ListParameterItem(WidgetParameterItem):
|
||||||
|
|
||||||
def setValue(self, val):
|
def setValue(self, val):
|
||||||
self.targetValue = val
|
self.targetValue = val
|
||||||
if val not in self.reverse[0]:
|
match = [fn.eq(val, limVal) for limVal in self.reverse[0]]
|
||||||
|
if not any(match):
|
||||||
self.widget.setCurrentIndex(0)
|
self.widget.setCurrentIndex(0)
|
||||||
else:
|
else:
|
||||||
key = self.reverse[1][self.reverse[0].index(val)]
|
idx = match.index(True)
|
||||||
|
key = self.reverse[1][idx]
|
||||||
ind = self.widget.findText(key)
|
ind = self.widget.findText(key)
|
||||||
self.widget.setCurrentIndex(ind)
|
self.widget.setCurrentIndex(ind)
|
||||||
|
|
||||||
|
@ -104,7 +107,9 @@ class ListParameter(Parameter):
|
||||||
self.forward, self.reverse = self.mapping(limits)
|
self.forward, self.reverse = self.mapping(limits)
|
||||||
|
|
||||||
Parameter.setLimits(self, limits)
|
Parameter.setLimits(self, limits)
|
||||||
if len(self.reverse[0]) > 0 and self.value() not in self.reverse[0]:
|
# 'value in limits' expression will break when reverse contains numpy array
|
||||||
|
curVal = self.value()
|
||||||
|
if len(self.reverse[0]) > 0 and not any(fn.eq(curVal, limVal) for limVal in self.reverse[0]):
|
||||||
self.setValue(self.reverse[0][0])
|
self.setValue(self.reverse[0][0])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -112,16 +117,11 @@ class ListParameter(Parameter):
|
||||||
# Return forward and reverse mapping objects given a limit specification
|
# Return forward and reverse mapping objects given a limit specification
|
||||||
forward = OrderedDict() ## {name: value, ...}
|
forward = OrderedDict() ## {name: value, ...}
|
||||||
reverse = ([], []) ## ([value, ...], [name, ...])
|
reverse = ([], []) ## ([value, ...], [name, ...])
|
||||||
if isinstance(limits, dict):
|
if not isinstance(limits, dict):
|
||||||
for k, v in limits.items():
|
limits = {str(l): l for l in limits}
|
||||||
forward[k] = v
|
for k, v in limits.items():
|
||||||
reverse[0].append(v)
|
forward[k] = v
|
||||||
reverse[1].append(k)
|
reverse[0].append(v)
|
||||||
else:
|
reverse[1].append(k)
|
||||||
for v in limits:
|
|
||||||
n = str(v)
|
|
||||||
forward[n] = v
|
|
||||||
reverse[0].append(v)
|
|
||||||
reverse[1].append(n)
|
|
||||||
return forward, reverse
|
return forward, reverse
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ class SliderParameterItem(WidgetParameterItem):
|
||||||
def makeWidget(self):
|
def makeWidget(self):
|
||||||
param = self.param
|
param = self.param
|
||||||
opts = param.opts
|
opts = param.opts
|
||||||
|
opts.setdefault('limits', [0, 0])
|
||||||
self._suffix = opts.get('suffix')
|
self._suffix = opts.get('suffix')
|
||||||
|
|
||||||
self.slider = QtWidgets.QSlider()
|
self.slider = QtWidgets.QSlider()
|
||||||
|
@ -94,7 +95,7 @@ class SliderParameterItem(WidgetParameterItem):
|
||||||
span = opts.get('span', None)
|
span = opts.get('span', None)
|
||||||
if span is None:
|
if span is None:
|
||||||
step = opts.get('step', 1)
|
step = opts.get('step', 1)
|
||||||
start, stop = opts['limits']
|
start, stop = opts.get('limits', param.opts['limits'])
|
||||||
# Add a bit to 'stop' since python slicing excludes the last value
|
# Add a bit to 'stop' since python slicing excludes the last value
|
||||||
span = np.arange(start, stop + step, step)
|
span = np.arange(start, stop + step, step)
|
||||||
precision = opts.get('precision', 2)
|
precision = opts.get('precision', 2)
|
||||||
|
|
Loading…
Reference in New Issue