2016-02-26 03:34:04 +08:00
|
|
|
import re
|
2016-08-11 22:51:56 +08:00
|
|
|
from math import inf, copysign
|
|
|
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
2016-02-26 03:34:04 +08:00
|
|
|
|
|
|
|
|
2016-08-11 22:51:56 +08:00
|
|
|
_float_acceptable = re.compile(
|
|
|
|
r"([-+]?\d*(?:\d|\.\d|\d\.)\d*)(?:[eE]([-+]?\d+))?",
|
|
|
|
)
|
|
|
|
_float_intermediate = re.compile(
|
|
|
|
r"[-+]?\d*\.?\d*(?:(?:(?<=\d)|(?<=\d\.))[eE][-+]?\d*)?",
|
|
|
|
)
|
|
|
|
_exp_shorten = re.compile(r"e\+?0*")
|
2016-02-26 03:34:04 +08:00
|
|
|
|
|
|
|
|
|
|
|
class ScientificSpinBox(QtWidgets.QDoubleSpinBox):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
super().__init__(*args, **kwargs)
|
2016-08-11 22:51:56 +08:00
|
|
|
self.setGroupSeparatorShown(False)
|
|
|
|
self.setInputMethodHints(QtCore.Qt.ImhNone)
|
|
|
|
self.setCorrectionMode(self.CorrectToPreviousValue)
|
|
|
|
# singleStep: resolution for step, buttons, accelerators
|
|
|
|
# decimals: absolute rounding granularity
|
|
|
|
# precision: number of significant digits shown, %g precision
|
|
|
|
self.setPrecision()
|
|
|
|
self.setRelativeStep()
|
|
|
|
self.setRange(-inf, inf)
|
|
|
|
self.setValue(0)
|
|
|
|
# self.setKeyboardTracking(False)
|
|
|
|
|
|
|
|
def setPrecision(self, d=None):
|
|
|
|
if d is None:
|
|
|
|
d = self.decimals() + 3
|
|
|
|
self._precision = max(1, int(d))
|
|
|
|
self._fmt = "{{:.{}g}}".format(self._precision)
|
|
|
|
|
|
|
|
def precision(self):
|
|
|
|
return self._precision
|
|
|
|
|
|
|
|
def setRelativeStep(self, s=None):
|
|
|
|
if s is None:
|
|
|
|
s = 1 + self.singleStep()
|
|
|
|
self._relative_step = max(1 + 10**-self.decimals(), float(s))
|
|
|
|
|
|
|
|
def relativeStep(self):
|
|
|
|
return self._relative_step
|
|
|
|
|
|
|
|
def setGroupSeparatorShown(self, s):
|
|
|
|
if s:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
def textFromValue(self, v):
|
|
|
|
t = self._fmt.format(v)
|
|
|
|
t = re.sub(_exp_shorten, "e", t, 1)
|
|
|
|
return t
|
2016-02-26 03:34:04 +08:00
|
|
|
|
|
|
|
def valueFromText(self, text):
|
2016-08-14 17:28:06 +08:00
|
|
|
clean = text
|
|
|
|
if self.prefix():
|
|
|
|
clean = clean.split(self.prefix(), 1)[-1]
|
|
|
|
if self.suffix():
|
|
|
|
clean = clean.rsplit(self.suffix(), 1)[0]
|
|
|
|
return round(float(clean), self.decimals())
|
2016-08-11 22:51:56 +08:00
|
|
|
|
|
|
|
def validate(self, text, pos):
|
2016-08-14 17:28:06 +08:00
|
|
|
clean = text
|
|
|
|
if self.prefix():
|
|
|
|
clean = clean.split(self.prefix(), 1)[-1]
|
|
|
|
if self.suffix():
|
|
|
|
clean = clean.rsplit(self.suffix(), 1)[0]
|
2016-08-11 22:51:56 +08:00
|
|
|
try:
|
2016-08-14 17:28:06 +08:00
|
|
|
float(clean) # faster than matching
|
2016-08-11 22:51:56 +08:00
|
|
|
return QtGui.QValidator.Acceptable, text, pos
|
|
|
|
except ValueError:
|
2016-08-14 17:28:06 +08:00
|
|
|
if re.fullmatch(_float_intermediate, clean):
|
2016-08-11 22:51:56 +08:00
|
|
|
return QtGui.QValidator.Intermediate, text, pos
|
|
|
|
return QtGui.QValidator.Invalid, text, pos
|
|
|
|
|
|
|
|
def stepBy(self, s):
|
|
|
|
if abs(s) < 10: # unaccelerated buttons, keys, wheel/trackpad
|
|
|
|
super().stepBy(s)
|
|
|
|
else: # accelerated PageUp/Down or CTRL-wheel
|
|
|
|
v = self.value()
|
|
|
|
v *= self._relative_step**(s/copysign(10., v))
|
|
|
|
self.setValue(v)
|