LP-311 Remove basic/advanced stabilization tab auto-switch (autotune/txpid lock issues)
[librepilot.git] / ground / gcs / src / libs / qscispinbox / QScienceSpinBox.cpp
blobaba3b968d2da2a75ba13c116c0fae2361a3e0459
1 /**
2 * Code copied from http://www.matthiaspospiech.de/blog/2009/01/03/qt-spinbox-widget-with-scientific-notation/
3 */
4 #include "QScienceSpinBox.h"
6 #include <limits>
8 // #define QSPINBOX_QSBDEBUG
9 #ifdef QSPINBOX_QSBDEBUG
10 # define QSBDEBUG qDebug
11 #else
12 # define QSBDEBUG \
13 if (false) \
14 qDebug
15 #endif
18 QScienceSpinBox::QScienceSpinBox(QWidget *parent)
19 : QDoubleSpinBox(parent)
21 initLocalValues(parent);
22 setDecimals(8);
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) {
43 thousand = str.at(1);
44 delimiter = str.at(5);
46 Q_ASSERT(!delimiter.isNull());
49 int QScienceSpinBox::decimals() const
51 return dispDecimals;
54 void QScienceSpinBox::setDecimals(int value)
56 dispDecimals = value;
59 // overwritten virtual function of QAbstractSpinBox
60 void QScienceSpinBox::stepBy(int steps)
62 if (steps < 0) {
63 stepDown();
64 } else {
65 stepUp();
69 void QScienceSpinBox::stepDown()
71 QSBDEBUG() << "stepDown()";
72 setValue(value() / 10.0);
75 void QScienceSpinBox::stepUp()
77 QSBDEBUG() << "stepUp()";
78 setValue(value() * 10.0);
81 /*!
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) {
91 str.remove(thousand);
93 return str;
96 double QScienceSpinBox::valueFromText(const QString &text) const
98 QString copy = text;
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);
119 return 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();
132 qint64 dec = 1;
134 for (int i = 0; i < decimals(); ++i) {
135 dec *= 10;
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;
158 bool doleft = true;
159 bool doright = true;
160 // no separator -> everthing in left
161 if (dotindex == -1) {
162 left = str.toLongLong();
163 doright = false;
165 // separator on left or contains '+'
166 else if (dotindex == 0 || (dotindex == 1 && str.at(0) == QLatin1Char('+'))) {
167 // '+' at negative max
168 if (negative) {
169 QSBDEBUG() << __FILE__ << __LINE__ << "returns false";
170 return false;
172 doleft = false;
173 right = str.mid(dotindex + 1).toLongLong();
175 // contains '-'
176 else if (dotindex == 1 && str.at(0) == QLatin1Char('-')) {
177 // '-' at positiv max
178 if (!negative) {
179 QSBDEBUG() << __FILE__ << __LINE__ << "returns false";
180 return false;
182 doleft = false;
183 right = str.mid(dotindex + 1).toLongLong();
184 } else {
185 left = str.left(dotindex).toLongLong();
186 if (dotindex == str.size() - 1) { // nothing right of Separator
187 doright = false;
188 } else {
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");
197 return false;
200 qint64 match = min_left;
201 if (doleft && !isIntermediateValueHelper(left, min_left, max_left, &match)) {
202 QSBDEBUG() << __FILE__ << __LINE__ << "returns false";
203 return false;
205 if (doright) {
206 QSBDEBUG("match %lld min_left %lld max_left %lld", match, min_left, max_left);
207 if (!doleft) {
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;
213 return 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;
218 return ret;
219 } else {
220 const bool ret = isIntermediateValueHelper(qAbs(left), 0, dec);
221 QSBDEBUG() << __FILE__ << __LINE__ << "returns" << ret;
222 return 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;
235 return ret;
237 QSBDEBUG() << __FILE__ << __LINE__ << "returns true";
238 return 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(
249 QString &input,
250 int &pos,
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()) {
262 state = cachedState;
263 QSBDEBUG() << "cachedText was" << "'" << cachedText << "'" << "state was "
264 << state << " and value was " << cachedValue;
265 return 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();
275 double num = min;
276 const bool plus = max >= 0;
277 const bool minus = min <= 0;
279 // Test possible 'Intermediate' reasons
280 switch (len) {
281 case 0:
282 // Length 0 is always 'Intermediate', except for min=max
283 if (max != min) {
284 state = QValidator::Intermediate;
285 } else {
286 state = QValidator::Invalid;
288 goto end;
289 case 1:
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;
295 goto end;
297 break;
298 case 2:
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;
303 goto end;
305 break;
306 default: break;
307 } // end switch
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;
314 goto end;
316 // Test possible 'Invalid' reasons
317 else if (len > 1) {
318 const int dec = copy.indexOf(delimiter); // position of delimiter
319 // if decimal separator (delimiter) exists
320 if (dec != -1) {
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;
329 goto end;
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;
336 goto end;
339 // if no decimal separator exists
340 } else {
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";
348 goto end;
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";
354 goto end;
357 } // end if (len > 1)
359 // block of remaining test before 'end' mark
361 bool ok = false;
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
371 if (!ok) {
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";
379 goto end;
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;
388 goto end;
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
401 if (!ok) {
402 state = QValidator::Invalid;
403 QSBDEBUG() << __FILE__ << __LINE__ << "state is set to Invalid";
404 goto end;
406 notAcceptable = true; // -> state = Intermediate
407 } // endif: (thousand.isPrint())
410 // no thousand sign, but still invalid for unknown reason
411 if (!ok) {
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) {
417 if (notAcceptable) {
418 state = QValidator::Intermediate; // conversion to num initially failed
419 } else {
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";
429 } else {
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";
434 } else {
435 // invalid range, further test with 'isIntermediateValue'
436 if (isIntermediateValue(copy)) {
437 state = QValidator::Intermediate;
438 } else {
439 state = QValidator::Invalid;
441 QSBDEBUG() << __FILE__ << __LINE__ << "state is set to "
442 << (state == QValidator::Intermediate ? "Intermediate" : "Acceptable");
447 end:
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();
455 cachedState = state;
456 cachedValue = QVariant(num);
457 // return resulting valid num
458 return QVariant(num);
463 \internal
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
469 QString text = t;
470 QString prefixtext = prefix();
471 QString suffixtext = suffix();
473 if (specialValueText().size() == 0 || text != specialValueText()) {
474 int from = 0;
475 int size = text.size();
476 bool changed = false;
477 if (prefixtext.size() && text.startsWith(prefixtext)) {
478 from += prefixtext.size();
479 size -= from;
480 changed = true;
482 if (suffixtext.size() && text.endsWith(suffixtext)) {
483 size -= suffixtext.size();
484 changed = true;
486 if (changed) {
487 text = text.mid(from, size);
491 const int s = text.size();
492 text = text.trimmed();
493 if (pos) {
494 (*pos) -= (s - text.size());
496 return text;
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) {
505 if (match) {
506 *match = num;
508 QSBDEBUG("returns true 0");
509 return true;
511 qint64 tmp = num;
513 int numDigits = 0;
514 int digits[10];
515 if (tmp == 0) {
516 numDigits = 1;
517 digits[0] = 0;
518 } else {
519 tmp = qAbs(num);
520 for (int i = 0; tmp > 0; ++i) {
521 digits[numDigits++] = tmp % 10;
522 tmp /= 10;
526 int failures = 0;
527 qint64 number;
528 for (number = max; number >= min; --number) {
529 tmp = qAbs(number);
530 for (int i = 0; tmp > 0;) {
531 if (digits[i] == (tmp % 10)) {
532 if (++i == numDigits) {
533 if (match) {
534 *match = number;
536 QSBDEBUG("returns true 1");
537 return true;
540 tmp /= 10;
542 if (failures++ == 500000) { // upper bound
543 if (match) {
544 *match = num;
546 QSBDEBUG("returns true 2");
547 return true;
550 QSBDEBUG("returns false");
551 return false;