Add spinbox 'regex' and 'evalFunc' options to complete user-formatting functionality
This commit is contained in:
parent
f0e26d3add
commit
e5a17edb4d
@ -13,7 +13,7 @@ import initExample ## Add path to library (just for examples; you do not need th
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph.Qt import QtCore, QtGui
|
||||
import numpy as np
|
||||
|
||||
import ast
|
||||
|
||||
app = QtGui.QApplication([])
|
||||
|
||||
@ -31,6 +31,13 @@ spins = [
|
||||
pg.SpinBox(value=1.0, suffix='V', siPrefix=True, dec=True, step=0.5, minStep=0.01)),
|
||||
("Float with SI-prefixed units,<br>dec step=1.0, minStep=0.001",
|
||||
pg.SpinBox(value=1.0, suffix='V', siPrefix=True, dec=True, step=1.0, minStep=0.001)),
|
||||
("Float with custom formatting",
|
||||
pg.SpinBox(value=23.07, format='${value:0.02f}',
|
||||
regex='\$?(?P<number>(-?\d+(\.\d+)?)|(-?\.\d+))$')),
|
||||
("Int with custom formatting",
|
||||
pg.SpinBox(value=4567, step=1, int=True, bounds=[0,None], format='0x{value:X}',
|
||||
regex='(0x)?(?P<number>[0-9a-fA-F]+)$',
|
||||
evalFunc=lambda s: ast.literal_eval('0x'+s))),
|
||||
]
|
||||
|
||||
|
||||
|
@ -37,8 +37,8 @@ SI_PREFIXES_ASCII = 'yzafpnum kMGTPEZY'
|
||||
SI_PREFIX_EXPONENTS = dict([(SI_PREFIXES[i], (i-8)*3) for i in range(len(SI_PREFIXES))])
|
||||
SI_PREFIX_EXPONENTS['u'] = -6
|
||||
|
||||
FLOAT_REGEX = re.compile(r'(?P<number>[+-]?((\d+(\.\d*)?)|(\d*\.\d+))([eE][+-]?\d+)?)\s*((?P<siprefix>[u' + SI_PREFIXES + r']?)(?P<suffix>\w.*))?$')
|
||||
INT_REGEX = re.compile(r'(?P<number>[+-]?\d+)\s*(?P<siprefix>[u' + SI_PREFIXES + r']?)(?P<suffix>.*)$')
|
||||
FLOAT_REGEX = re.compile(r'(?P<number>[+-]?((\d+(\.\d*)?)|(\d*\.\d+))([eE][+-]?\d+)?)\s*((?P<siPrefix>[u' + SI_PREFIXES + r']?)(?P<suffix>\w.*))?$')
|
||||
INT_REGEX = re.compile(r'(?P<number>[+-]?\d+)\s*(?P<siPrefix>[u' + SI_PREFIXES + r']?)(?P<suffix>.*)$')
|
||||
|
||||
|
||||
def siScale(x, minVal=1e-25, allowUnicode=True):
|
||||
@ -121,8 +121,16 @@ def siParse(s, regex=FLOAT_REGEX):
|
||||
m = regex.match(s)
|
||||
if m is None:
|
||||
raise ValueError('Cannot parse number "%s"' % s)
|
||||
sip = m.group('siprefix')
|
||||
try:
|
||||
sip = m.group('siPrefix')
|
||||
except IndexError:
|
||||
sip = ''
|
||||
|
||||
try:
|
||||
suf = m.group('suffix')
|
||||
except IndexError:
|
||||
suf = ''
|
||||
|
||||
return m.group('number'), '' if sip is None else sip, '' if suf is None else suf
|
||||
|
||||
|
||||
|
@ -1,13 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from ..Qt import QtGui, QtCore
|
||||
from ..python2_3 import asUnicode
|
||||
from ..SignalProxy import SignalProxy
|
||||
|
||||
from .. import functions as fn
|
||||
from math import log
|
||||
from decimal import Decimal as D ## Use decimal to avoid accumulating floating-point errors
|
||||
import decimal
|
||||
import weakref
|
||||
import re
|
||||
|
||||
from ..Qt import QtGui, QtCore
|
||||
from ..python2_3 import asUnicode, basestring
|
||||
from ..SignalProxy import SignalProxy
|
||||
from .. import functions as fn
|
||||
|
||||
|
||||
__all__ = ['SpinBox']
|
||||
@ -89,6 +90,8 @@ class SpinBox(QtGui.QAbstractSpinBox):
|
||||
'decimals': 6,
|
||||
|
||||
'format': asUnicode("{scaledValue:.{decimals}g}{suffixGap}{siPrefix}{suffix}"),
|
||||
'regex': fn.FLOAT_REGEX,
|
||||
'evalFunc': D,
|
||||
|
||||
'compactHeight': True, # manually remove extra margin outside of text
|
||||
}
|
||||
@ -152,28 +155,41 @@ class SpinBox(QtGui.QAbstractSpinBox):
|
||||
this feature has been disabled
|
||||
* *suffixGap* - a single space if a suffix is present, or an empty
|
||||
string otherwise.
|
||||
regex (str or RegexObject) Regular expression used to parse the spinbox text.
|
||||
May contain the following group names:
|
||||
|
||||
* *number* - matches the numerical portion of the string (mandatory)
|
||||
* *siPrefix* - matches the SI prefix string
|
||||
* *suffix* - matches the suffix string
|
||||
|
||||
Default is defined in ``pyqtgraph.functions.FLOAT_REGEX``.
|
||||
evalFunc (callable) Fucntion that converts a numerical string to a number,
|
||||
preferrably a Decimal instance. This function handles only the numerical
|
||||
of the text; it does not have access to the suffix or SI prefix.
|
||||
compactHeight (bool) if True, then set the maximum height of the spinbox based on the
|
||||
height of its font. This allows more compact packing on platforms with
|
||||
excessive widget decoration. Default is True.
|
||||
============== ========================================================================
|
||||
"""
|
||||
#print opts
|
||||
for k in opts:
|
||||
for k,v in opts.items():
|
||||
if k == 'bounds':
|
||||
self.setMinimum(opts[k][0], update=False)
|
||||
self.setMaximum(opts[k][1], update=False)
|
||||
self.setMinimum(v[0], update=False)
|
||||
self.setMaximum(v[1], update=False)
|
||||
elif k == 'min':
|
||||
self.setMinimum(opts[k], update=False)
|
||||
self.setMinimum(v, update=False)
|
||||
elif k == 'max':
|
||||
self.setMaximum(opts[k], update=False)
|
||||
self.setMaximum(v, update=False)
|
||||
elif k in ['step', 'minStep']:
|
||||
self.opts[k] = D(asUnicode(opts[k]))
|
||||
self.opts[k] = D(asUnicode(v))
|
||||
elif k == 'value':
|
||||
pass ## don't set value until bounds have been set
|
||||
elif k == 'format':
|
||||
self.opts[k] = asUnicode(opts[k])
|
||||
self.opts[k] = asUnicode(v)
|
||||
elif k == 'regex' and isinstance(v, basestring):
|
||||
self.opts[k] = re.compile(v)
|
||||
elif k in self.opts:
|
||||
self.opts[k] = opts[k]
|
||||
self.opts[k] = v
|
||||
else:
|
||||
raise TypeError("Invalid keyword argument '%s'." % k)
|
||||
if 'value' in opts:
|
||||
@ -266,14 +282,11 @@ class SpinBox(QtGui.QAbstractSpinBox):
|
||||
"""
|
||||
le = self.lineEdit()
|
||||
text = asUnicode(le.text())
|
||||
if self.opts['suffix'] == '':
|
||||
le.setSelection(0, len(text))
|
||||
else:
|
||||
try:
|
||||
index = text.index(' ')
|
||||
except ValueError:
|
||||
m = self.opts['regex'].match(text)
|
||||
if m is None:
|
||||
return
|
||||
le.setSelection(0, index)
|
||||
s,e = m.start('number'), m.end('number')
|
||||
le.setSelection(s, e-s)
|
||||
|
||||
def focusInEvent(self, ev):
|
||||
super(SpinBox, self).focusInEvent(ev)
|
||||
@ -483,7 +496,7 @@ class SpinBox(QtGui.QAbstractSpinBox):
|
||||
|
||||
# tokenize into numerical value, si prefix, and suffix
|
||||
try:
|
||||
val, siprefix, suffix = fn.siParse(strn)
|
||||
val, siprefix, suffix = fn.siParse(strn, self.opts['regex'])
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@ -492,7 +505,7 @@ class SpinBox(QtGui.QAbstractSpinBox):
|
||||
return False
|
||||
|
||||
# generate value
|
||||
val = D(val)
|
||||
val = self.opts['evalFunc'](val)
|
||||
if self.opts['int']:
|
||||
val = int(fn.siApply(val, siprefix))
|
||||
else:
|
||||
|
@ -1,6 +1,7 @@
|
||||
import pyqtgraph as pg
|
||||
pg.mkQApp()
|
||||
|
||||
|
||||
def test_spinbox():
|
||||
sb = pg.SpinBox()
|
||||
assert sb.opts['decimals'] == 3
|
||||
@ -13,8 +14,10 @@ def test_spinbox():
|
||||
(100, '100', dict()),
|
||||
(1000000, '1e+06', dict()),
|
||||
(1000, '1e+03', dict(decimals=2)),
|
||||
(1000000, '1000000', dict(int=True)),
|
||||
(12345678955, '12345678955', dict(int=True)),
|
||||
(1000000, '1e+06', dict(int=True, decimals=6)),
|
||||
(12345678955, '12345678955', dict(int=True, decimals=100)),
|
||||
(1.45e-9, '1.45e-9 A', dict(int=False, decimals=6, suffix='A', siPrefix=False)),
|
||||
(1.45e-9, '1.45 nA', dict(int=False, decimals=6, suffix='A', siPrefix=True)),
|
||||
]
|
||||
|
||||
for (value, text, opts) in conds:
|
||||
|
Loading…
Reference in New Issue
Block a user