2 * Code copied from http://www.matthiaspospiech.de/blog/2009/01/03/qt-spinbox-widget-with-scientific-notation/
4 #include "QScienceSpinBox.h"
8 // #define QSPINBOX_QSBDEBUG
9 #ifdef QSPINBOX_QSBDEBUG
10 # define QSBDEBUG qDebug
18 QScienceSpinBox::QScienceSpinBox(QWidget
*parent
)
19 : QDoubleSpinBox(parent
)
21 initLocalValues(parent
);
23 QDoubleSpinBox::setDecimals(1000);
25 // set Range to maximum possible values
26 double doubleMax
= std::numeric_limits
<double>::max();
27 setRange(-doubleMax
, doubleMax
);
29 v
= new QDoubleValidator(this);
30 v
->setDecimals(1000); // (standard anyway)
31 v
->setNotation(QDoubleValidator::ScientificNotation
);
32 this->lineEdit()->setValidator(v
);
35 void QScienceSpinBox::initLocalValues(QWidget
*parent
)
37 const QString str
= (parent
? parent
->locale() : QLocale()).toString(4567.1);
39 if (str
.size() == 6) {
40 delimiter
= str
.at(4);
41 thousand
= QChar((ushort
)0);
42 } else if (str
.size() == 7) {
44 delimiter
= str
.at(5);
46 Q_ASSERT(!delimiter
.isNull());
49 int QScienceSpinBox::decimals() const
54 void QScienceSpinBox::setDecimals(int value
)
59 // overwritten virtual function of QAbstractSpinBox
60 void QScienceSpinBox::stepBy(int steps
)
69 void QScienceSpinBox::stepDown()
71 QSBDEBUG() << "stepDown()";
72 setValue(value() / 10.0);
75 void QScienceSpinBox::stepUp()
77 QSBDEBUG() << "stepUp()";
78 setValue(value() * 10.0);
82 * text to be displayed in spinbox
84 QString
QScienceSpinBox::textFromValue(double value
) const
86 // convert to string -> Using exponetial display with internal decimals
87 QString str
= locale().toString(value
, 'e', dispDecimals
);
89 // remove thousand sign
90 if (qAbs(value
) >= 1000.0) {
96 double QScienceSpinBox::valueFromText(const QString
&text
) const
99 int pos
= this->lineEdit()->cursorPosition();
100 QValidator::State state
= QValidator::Acceptable
;
102 return validateAndInterpret(copy
, pos
, state
).toDouble();
105 // this function is never used...?
106 double QScienceSpinBox::round(double value
) const
108 const QString strDbl
= locale().toString(value
, 'g', dispDecimals
);
110 return locale().toDouble(strDbl
);
113 // overwritten virtual function of QAbstractSpinBox
114 QValidator::State
QScienceSpinBox::validate(QString
&text
, int &pos
) const
116 QValidator::State state
;
118 validateAndInterpret(text
, pos
, state
);
122 // overwritten virtual function of QAbstractSpinBox
123 void QScienceSpinBox::fixup(QString
&input
) const
125 input
.remove(thousand
);
128 // reimplemented function, copied from QDoubleSpinBoxPrivate::isIntermediateValue
129 bool QScienceSpinBox::isIntermediateValue(const QString
&str
) const
131 QSBDEBUG() << "input is" << str
<< minimum() << maximum();
134 for (int i
= 0; i
< decimals(); ++i
) {
138 const QLatin1Char
dot('.');
141 * determine minimum possible values on left and right of Decimal-char
143 // I know QString::number() uses CLocale so I use dot
144 const QString minstr
= QString::number(minimum(), 'f', QDoubleSpinBox::decimals());
145 qint64 min_left
= minstr
.left(minstr
.indexOf(dot
)).toLongLong();
146 qint64 min_right
= minstr
.mid(minstr
.indexOf(dot
) + 1).toLongLong();
148 const QString maxstr
= QString::number(maximum(), 'f', QDoubleSpinBox::decimals());
149 qint64 max_left
= maxstr
.left(maxstr
.indexOf(dot
)).toLongLong();
150 qint64 max_right
= maxstr
.mid(maxstr
.indexOf(dot
) + 1).toLongLong();
153 * determine left and right long values (left and right of delimiter)
155 const int dotindex
= str
.indexOf(delimiter
);
156 const bool negative
= maximum() < 0;
157 qint64 left
= 0, right
= 0;
160 // no separator -> everthing in left
161 if (dotindex
== -1) {
162 left
= str
.toLongLong();
165 // separator on left or contains '+'
166 else if (dotindex
== 0 || (dotindex
== 1 && str
.at(0) == QLatin1Char('+'))) {
167 // '+' at negative max
169 QSBDEBUG() << __FILE__
<< __LINE__
<< "returns false";
173 right
= str
.mid(dotindex
+ 1).toLongLong();
176 else if (dotindex
== 1 && str
.at(0) == QLatin1Char('-')) {
177 // '-' at positiv max
179 QSBDEBUG() << __FILE__
<< __LINE__
<< "returns false";
183 right
= str
.mid(dotindex
+ 1).toLongLong();
185 left
= str
.left(dotindex
).toLongLong();
186 if (dotindex
== str
.size() - 1) { // nothing right of Separator
189 right
= str
.mid(dotindex
+ 1).toLongLong();
192 // left > 0, with max < 0 and no '-'
193 if ((left
>= 0 && max_left
< 0 && !str
.startsWith(QLatin1Char('-')))
194 // left > 0, with min > 0
195 || (left
< 0 && min_left
>= 0)) {
196 QSBDEBUG("returns false");
200 qint64 match
= min_left
;
201 if (doleft
&& !isIntermediateValueHelper(left
, min_left
, max_left
, &match
)) {
202 QSBDEBUG() << __FILE__
<< __LINE__
<< "returns false";
206 QSBDEBUG("match %lld min_left %lld max_left %lld", match
, min_left
, max_left
);
208 if (min_left
== max_left
) {
209 const bool ret
= isIntermediateValueHelper(qAbs(left
),
210 negative
? max_right
: min_right
,
211 negative
? min_right
: max_right
);
212 QSBDEBUG() << __FILE__
<< __LINE__
<< "returns" << ret
;
214 } else if (qAbs(max_left
- min_left
) == 1) {
215 const bool ret
= isIntermediateValueHelper(qAbs(left
), min_right
, negative
? 0 : dec
)
216 || isIntermediateValueHelper(qAbs(left
), negative
? dec
: 0, max_right
);
217 QSBDEBUG() << __FILE__
<< __LINE__
<< "returns" << ret
;
220 const bool ret
= isIntermediateValueHelper(qAbs(left
), 0, dec
);
221 QSBDEBUG() << __FILE__
<< __LINE__
<< "returns" << ret
;
225 if (match
!= min_left
) {
226 min_right
= negative
? dec
: 0;
228 if (match
!= max_left
) {
229 max_right
= negative
? 0 : dec
;
231 qint64 tmpl
= negative
? max_right
: min_right
;
232 qint64 tmpr
= negative
? min_right
: max_right
;
233 const bool ret
= isIntermediateValueHelper(right
, tmpl
, tmpr
);
234 QSBDEBUG() << __FILE__
<< __LINE__
<< "returns" << ret
;
237 QSBDEBUG() << __FILE__
<< __LINE__
<< "returns true";
243 \internal Multi purpose function that parses input, sets state to
244 the appropriate state and returns the value it will be interpreted
247 // reimplemented function, copied from QDoubleSpinBoxPrivate::validateAndInterpret
248 QVariant
QScienceSpinBox::validateAndInterpret(
251 QValidator::State
&state
) const
253 /*! return 'cachedText' if
254 * input = cachedText, or input Empty
257 static QString cachedText
;
258 static QValidator::State cachedState
;
259 static QVariant cachedValue
;
261 if (cachedText
== input
&& !input
.isEmpty()) {
263 QSBDEBUG() << "cachedText was" << "'" << cachedText
<< "'" << "state was "
264 << state
<< " and value was " << cachedValue
;
267 const double max
= maximum();
268 const double min
= minimum();
270 // removes prefix & suffix
271 QString copy
= stripped(input
, &pos
);
272 QSBDEBUG() << "input" << input
<< "copy" << copy
;
274 int len
= copy
.size();
276 const bool plus
= max
>= 0;
277 const bool minus
= min
<= 0;
279 // Test possible 'Intermediate' reasons
282 // Length 0 is always 'Intermediate', except for min=max
284 state
= QValidator::Intermediate
;
286 state
= QValidator::Invalid
;
290 // if only char is '+' or '-'
291 if (copy
.at(0) == delimiter
292 || (plus
&& copy
.at(0) == QLatin1Char('+'))
293 || (minus
&& copy
.at(0) == QLatin1Char('-'))) {
294 state
= QValidator::Intermediate
;
299 // if only chars are '+' or '-' followed by Comma seperator (delimiter)
300 if (copy
.at(1) == delimiter
301 && ((plus
&& copy
.at(0) == QLatin1Char('+')) || (minus
&& copy
.at(0) == QLatin1Char('-')))) {
302 state
= QValidator::Intermediate
;
310 // First char must not be thousand-char
311 if (copy
.at(0) == thousand
) {
312 QSBDEBUG() << __FILE__
<< __LINE__
<< "state is set to Invalid";
313 state
= QValidator::Invalid
;
316 // Test possible 'Invalid' reasons
318 const int dec
= copy
.indexOf(delimiter
); // position of delimiter
319 // if decimal separator (delimiter) exists
321 // not two delimiters after one other (meaning something like ',,')
322 if (dec
+ 1 < copy
.size() && copy
.at(dec
+ 1) == delimiter
&& pos
== dec
+ 1) {
323 copy
.remove(dec
+ 1, 1); // typing a delimiter when you are on the delimiter
324 } // should be treated as typing right arrow
325 // too many decimal points
326 if (copy
.size() - dec
> QDoubleSpinBox::decimals() + 1) {
327 QSBDEBUG() << __FILE__
<< __LINE__
<< "state is set to Invalid";
328 state
= QValidator::Invalid
;
331 // after decimal separator no thousand char
332 for (int i
= dec
+ 1; i
< copy
.size(); ++i
) {
333 if (copy
.at(i
).isSpace() || copy
.at(i
) == thousand
) {
334 QSBDEBUG() << __FILE__
<< __LINE__
<< "state is set to Invalid";
335 state
= QValidator::Invalid
;
339 // if no decimal separator exists
341 const QChar
&last
= copy
.at(len
- 1);
342 const QChar
&secondLast
= copy
.at(len
- 2);
343 // group of two thousand or space chars is invalid
344 if ((last
== thousand
|| last
.isSpace())
345 && (secondLast
== thousand
|| secondLast
.isSpace())) {
346 state
= QValidator::Invalid
;
347 QSBDEBUG() << __FILE__
<< __LINE__
<< "state is set to Invalid";
350 // two space chars is invalid
351 else if (last
.isSpace() && (!thousand
.isSpace() || secondLast
.isSpace())) {
352 state
= QValidator::Invalid
;
353 QSBDEBUG() << __FILE__
<< __LINE__
<< "state is set to Invalid";
357 } // end if (len > 1)
359 // block of remaining test before 'end' mark
362 bool notAcceptable
= false;
364 // convert 'copy' to double, and check if that was 'ok'
365 QLocale
loc(locale());
366 num
= loc
.toDouble(copy
, &ok
);
367 QSBDEBUG() << __FILE__
<< __LINE__
<< loc
<< copy
<< num
<< ok
;
370 // conversion to double did fail
372 // maybe thousand char was responsable
373 if (thousand
.isPrint()) {
374 // if no thousand sign is possible, then
375 // something else is responable -> Invalid
376 if (max
< 1000 && min
> -1000 && copy
.contains(thousand
)) {
377 state
= QValidator::Invalid
;
378 QSBDEBUG() << __FILE__
<< __LINE__
<< "state is set to Invalid";
382 // two thousand-chars after one other are not valid
383 const int len
= copy
.size();
384 for (int i
= 0; i
< len
- 1; ++i
) {
385 if (copy
.at(i
) == thousand
&& copy
.at(i
+ 1) == thousand
) {
386 QSBDEBUG() << __FILE__
<< __LINE__
<< "state is set to Invalid";
387 state
= QValidator::Invalid
;
392 // remove thousand-chars
393 const int s
= copy
.size();
394 copy
.remove(thousand
);
395 pos
= qMax(0, pos
- (s
- copy
.size()));
397 num
= loc
.toDouble(copy
, &ok
);
398 QSBDEBUG() << thousand
<< num
<< copy
<< ok
;
400 // if conversion still not valid, then reason unknown -> Invalid
402 state
= QValidator::Invalid
;
403 QSBDEBUG() << __FILE__
<< __LINE__
<< "state is set to Invalid";
406 notAcceptable
= true; // -> state = Intermediate
407 } // endif: (thousand.isPrint())
410 // no thousand sign, but still invalid for unknown reason
412 state
= QValidator::Invalid
;
413 QSBDEBUG() << __FILE__
<< __LINE__
<< "state is set to Invalid";
415 // number valid and within valid range
416 else if (num
>= min
&& num
<= max
) {
418 state
= QValidator::Intermediate
; // conversion to num initially failed
420 state
= QValidator::Acceptable
;
422 QSBDEBUG() << __FILE__
<< __LINE__
<< "state is set to "
423 << (state
== QValidator::Intermediate
? "Intermediate" : "Acceptable");
425 // when max and min is the same the only non-Invalid input is max (or min)
426 else if (max
== min
) {
427 state
= QValidator::Invalid
;
428 QSBDEBUG() << __FILE__
<< __LINE__
<< "state is set to Invalid";
430 // value out of valid range (coves only special cases)
431 if ((num
>= 0 && num
> max
) || (num
< 0 && num
< min
)) {
432 state
= QValidator::Invalid
;
433 QSBDEBUG() << __FILE__
<< __LINE__
<< "state is set to Invalid";
435 // invalid range, further test with 'isIntermediateValue'
436 if (isIntermediateValue(copy
)) {
437 state
= QValidator::Intermediate
;
439 state
= QValidator::Invalid
;
441 QSBDEBUG() << __FILE__
<< __LINE__
<< "state is set to "
442 << (state
== QValidator::Intermediate
? "Intermediate" : "Acceptable");
448 // if something went wrong, set num to something valid
449 if (state
!= QValidator::Acceptable
) {
450 num
= max
> 0 ? min
: max
;
453 // save (private) cache values
454 cachedText
= prefix() + copy
+ suffix();
456 cachedValue
= QVariant(num
);
457 // return resulting valid num
458 return QVariant(num
);
464 Strips any prefix/suffix from \a text.
466 // reimplemented function, copied from QAbstractSpinBoxPrivate::stripped
467 QString
QScienceSpinBox::stripped(const QString
&t
, int *pos
) const
470 QString prefixtext
= prefix();
471 QString suffixtext
= suffix();
473 if (specialValueText().size() == 0 || text
!= specialValueText()) {
475 int size
= text
.size();
476 bool changed
= false;
477 if (prefixtext
.size() && text
.startsWith(prefixtext
)) {
478 from
+= prefixtext
.size();
482 if (suffixtext
.size() && text
.endsWith(suffixtext
)) {
483 size
-= suffixtext
.size();
487 text
= text
.mid(from
, size
);
491 const int s
= text
.size();
492 text
= text
.trimmed();
494 (*pos
) -= (s
- text
.size());
499 // reimplemented function, copied from qspinbox.cpp
500 bool QScienceSpinBox::isIntermediateValueHelper(qint64 num
, qint64 min
, qint64 max
, qint64
*match
)
502 QSBDEBUG("%lld %lld %lld", num
, min
, max
);
504 if (num
>= min
&& num
<= max
) {
508 QSBDEBUG("returns true 0");
520 for (int i
= 0; tmp
> 0; ++i
) {
521 digits
[numDigits
++] = tmp
% 10;
528 for (number
= max
; number
>= min
; --number
) {
530 for (int i
= 0; tmp
> 0;) {
531 if (digits
[i
] == (tmp
% 10)) {
532 if (++i
== numDigits
) {
536 QSBDEBUG("returns true 1");
542 if (failures
++ == 500000) { // upper bound
546 QSBDEBUG("returns true 2");
550 QSBDEBUG("returns false");