Mantid
Loading...
Searching...
No Matches
QScienceSpinBox.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
8#include <limits>
9
10//#define QSPINBOX_QSBDEBUG
11#ifdef QSPINBOX_QSBDEBUG
12#define QSBDEBUG qDebug
13#else
14#define QSBDEBUG \
15 if (false) \
16 qDebug
17#endif
18
19namespace MantidQt {
20namespace API {
21
22// reimplemented function, copied from qspinbox.cpp
23bool isIntermediateValueHelper(qint64 num, qint64 min, qint64 max, qint64 *match = nullptr) {
24 QSBDEBUG("%lld %lld %lld", num, min, max);
25
26 if (num >= min && num <= max) {
27 if (match)
28 *match = num;
29 QSBDEBUG("returns true 0");
30 return true;
31 }
32 qint64 tmp = num;
33
34 int numDigits = 0;
35 int digits[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
36 if (tmp == 0) {
37 numDigits = 1;
38 digits[0] = 0;
39 } else {
40 tmp = qAbs(num);
41 for (int i = 0; tmp > 0; ++i) {
42 digits[numDigits++] = int(tmp) % 10;
43 tmp /= 10;
44 }
45 }
46
47 int failures = 0;
48 qint64 number;
49 for (number = max; number >= min; --number) {
50 tmp = qAbs(number);
51 for (int i = 0; tmp > 0;) {
52 if (digits[i] == (tmp % 10)) {
53 if (++i == numDigits) {
54 if (match)
55 *match = number;
56 QSBDEBUG("returns true 1");
57 return true;
58 }
59 }
60 tmp /= 10;
61 }
62 if (failures++ == 500000) { // upper bound
63 if (match)
64 *match = num;
65 QSBDEBUG("returns true 2");
66 return true;
67 }
68 }
69 QSBDEBUG("returns false");
70 return false;
71}
72
73QScienceSpinBox::QScienceSpinBox(QWidget *parent) : QDoubleSpinBox(parent), m_logSteps(true) {
74 initLocalValues(parent);
75 setDecimals(8);
76 QDoubleSpinBox::setDecimals(1000);
77
78 // set Range to maximum possible values
79 double doubleMax = std::numeric_limits<double>::max();
80 setRange(-doubleMax, doubleMax);
81
82 v = new QDoubleValidator(this);
83 v->setDecimals(1000); // (standard anyway)
84 v->setNotation(QDoubleValidator::ScientificNotation);
85 this->lineEdit()->setValidator(v);
86}
87
88void QScienceSpinBox::initLocalValues(QWidget *parent) {
89 const QString str = (parent ? parent->locale() : QLocale()).toString(4567.1);
90 if (str.size() == 6) {
91 delimiter = str.at(4);
92 thousand = QChar((ushort)0);
93 } else if (str.size() == 7) {
94 thousand = str.at(1);
95 delimiter = str.at(5);
96 }
97 Q_ASSERT(!delimiter.isNull());
98}
99
101
103
104// overwritten virtual function of QAbstractSpinBox
105void QScienceSpinBox::stepBy(int steps) {
106 if (steps < 0)
107 stepDown();
108 else
109 stepUp();
110}
111
112void QScienceSpinBox::setLogSteps(bool logSteps) { m_logSteps = logSteps; }
113
115 if (m_logSteps)
116 setValue(value() / this->singleStep());
117 else
118 setValue(value() - this->singleStep());
120}
121
123 if (m_logSteps)
124 setValue(value() * this->singleStep());
125 else
126 setValue(value() + this->singleStep());
128}
129
134
135 // convert to string -> Using exponetial display with internal decimals
136 QString str = locale().toString(value, 'e', dispDecimals);
137 // remove thousand sign
138 if (qAbs(value) >= 1000.0) {
139 str.remove(thousand);
140 }
141 return str;
142}
143
144double QScienceSpinBox::valueFromText(const QString &text) const {
145 QString copy = text;
146 int pos = this->lineEdit()->cursorPosition();
147 QValidator::State state = QValidator::Acceptable;
148 return validateAndInterpret(copy, pos, state).toDouble();
149}
150
156double QScienceSpinBox::round(double value) const {
157 // this function is never used...?
158 const QString strDbl = locale().toString(value, 'g', dispDecimals);
159 return locale().toDouble(strDbl);
160}
161
162// overwritten virtual function of QAbstractSpinBox
163QValidator::State QScienceSpinBox::validate(QString &text, int &pos) const {
164 QValidator::State state;
165 validateAndInterpret(text, pos, state);
166 return state;
167}
168
169// overwritten virtual function of QAbstractSpinBox
170void QScienceSpinBox::fixup(QString &input) const { input.remove(thousand); }
171
172// reimplemented function, copied from
173// QDoubleSpinBoxPrivate::isIntermediateValue
174bool QScienceSpinBox::isIntermediateValue(const QString &str) const {
175 QSBDEBUG() << "input is" << str << minimum() << maximum();
176 qint64 dec = 1;
177
178 for (int i = 0; i < decimals(); ++i)
179 dec *= 10;
180
181 const QLatin1Char dot('.');
182
186 // I know QString::number() uses CLocale so I use dot
187 const QString minstr = QString::number(minimum(), 'f', QDoubleSpinBox::decimals());
188 qint64 min_left = minstr.left(minstr.indexOf(dot)).toLongLong();
189 qint64 min_right = minstr.mid(minstr.indexOf(dot) + 1).toLongLong();
190
191 const QString maxstr = QString::number(maximum(), 'f', QDoubleSpinBox::decimals());
192 qint64 max_left = maxstr.left(maxstr.indexOf(dot)).toLongLong();
193 qint64 max_right = maxstr.mid(maxstr.indexOf(dot) + 1).toLongLong();
194
198 const int dotindex = str.indexOf(delimiter);
199 const bool negative = maximum() < 0;
200 qint64 left = 0, right = 0;
201 bool doleft = true;
202 bool doright = true;
203 // no separator -> everthing in left
204 if (dotindex == -1) {
205 left = str.toLongLong();
206 doright = false;
207 }
208 // separator on left or contains '+'
209 else if (dotindex == 0 || (dotindex == 1 && str.at(0) == QLatin1Char('+'))) {
210 // '+' at negative max
211 if (negative) {
212 QSBDEBUG() << __FILE__ << __LINE__ << "returns false";
213 return false;
214 }
215 doleft = false;
216 right = str.mid(dotindex + 1).toLongLong();
217 }
218 // contains '-'
219 else if (dotindex == 1 && str.at(0) == QLatin1Char('-')) {
220 // '-' at positiv max
221 if (!negative) {
222 QSBDEBUG() << __FILE__ << __LINE__ << "returns false";
223 return false;
224 }
225 doleft = false;
226 right = str.mid(dotindex + 1).toLongLong();
227 } else {
228 left = str.left(dotindex).toLongLong();
229 if (dotindex == str.size() - 1) { // nothing right of Separator
230 doright = false;
231 } else {
232 right = str.mid(dotindex + 1).toLongLong();
233 }
234 }
235 // left > 0, with max < 0 and no '-'
236 if ((left >= 0 && max_left < 0 && !str.startsWith(QLatin1Char('-')))
237 // left > 0, with min > 0
238 || (left < 0 && min_left >= 0)) {
239 QSBDEBUG("returns false");
240 return false;
241 }
242
243 qint64 match = min_left;
244 if (doleft && !isIntermediateValueHelper(left, min_left, max_left, &match)) {
245 QSBDEBUG() << __FILE__ << __LINE__ << "returns false";
246 return false;
247 }
248 if (doright) {
249 QSBDEBUG("match %lld min_left %lld max_left %lld", match, min_left, max_left);
250 if (!doleft) {
251 if (min_left == max_left) {
252 const bool ret =
253 isIntermediateValueHelper(qAbs(left), negative ? max_right : min_right, negative ? min_right : max_right);
254 QSBDEBUG() << __FILE__ << __LINE__ << "returns" << ret;
255 return ret;
256 } else if (qAbs(max_left - min_left) == 1) {
257 const bool ret = isIntermediateValueHelper(qAbs(left), min_right, negative ? 0 : dec) ||
258 isIntermediateValueHelper(qAbs(left), negative ? dec : 0, max_right);
259 QSBDEBUG() << __FILE__ << __LINE__ << "returns" << ret;
260 return ret;
261 } else {
262 const bool ret = isIntermediateValueHelper(qAbs(left), 0, dec);
263 QSBDEBUG() << __FILE__ << __LINE__ << "returns" << ret;
264 return ret;
265 }
266 }
267 if (match != min_left) {
268 min_right = negative ? dec : 0;
269 }
270 if (match != max_left) {
271 max_right = negative ? 0 : dec;
272 }
273 qint64 tmpl = negative ? max_right : min_right;
274 qint64 tmpr = negative ? min_right : max_right;
275 const bool ret = isIntermediateValueHelper(right, tmpl, tmpr);
276 QSBDEBUG() << __FILE__ << __LINE__ << "returns" << ret;
277 return ret;
278 }
279 QSBDEBUG() << __FILE__ << __LINE__ << "returns true";
280 return true;
281}
282
288// reimplemented function, copied from
289// QDoubleSpinBoxPrivate::validateAndInterpret
290QVariant QScienceSpinBox::validateAndInterpret(QString &input, int &pos, QValidator::State &state) const {
295 static QString cachedText;
296 static QValidator::State cachedState;
297 static QVariant cachedValue;
298
299 if (cachedText == input && !input.isEmpty()) {
300 state = cachedState;
301 QSBDEBUG() << "cachedText was"
302 << "'" << cachedText << "'"
303 << "state was " << state << " and value was " << cachedValue;
304 return cachedValue;
305 }
306 const double max = maximum();
307 const double min = minimum();
308
309 // removes prefix & suffix
310 QString copy = stripped(input, &pos);
311 QSBDEBUG() << "input" << input << "copy" << copy;
312
313 int len = copy.size();
314 double num = min;
315 const bool plus = max >= 0;
316 const bool minus = min <= 0;
317
318 // Test possible 'Intermediate' reasons
319 switch (len) {
320 case 0:
321 // Length 0 is always 'Intermediate', except for min=max
322 if (max != min) {
323 state = QValidator::Intermediate;
324 } else {
325 state = QValidator::Invalid;
326 }
327 goto end;
328 case 1:
329 // if only char is '+' or '-'
330 if (copy.at(0) == delimiter || (plus && copy.at(0) == QLatin1Char('+')) ||
331 (minus && copy.at(0) == QLatin1Char('-'))) {
332 state = QValidator::Intermediate;
333 goto end;
334 }
335 break;
336 case 2:
337 // if only chars are '+' or '-' followed by Comma seperator (delimiter)
338 if (copy.at(1) == delimiter &&
339 ((plus && copy.at(0) == QLatin1Char('+')) || (minus && copy.at(0) == QLatin1Char('-')))) {
340 state = QValidator::Intermediate;
341 goto end;
342 }
343 break;
344 default:
345 break;
346 } // end switch
347
348 // First char must not be thousand-char
349 if (copy.at(0) == thousand) {
350 QSBDEBUG() << __FILE__ << __LINE__ << "state is set to Invalid";
351 state = QValidator::Invalid;
352 goto end;
353 }
354 // Test possible 'Invalid' reasons
355 else if (len > 1) {
356 const int dec = copy.indexOf(delimiter); // position of delimiter
357 // if decimal separator (delimiter) exists
358 if (dec != -1) {
359 // not two delimiters after one other (meaning something like ',,')
360 if (dec + 1 < copy.size() && copy.at(dec + 1) == delimiter && pos == dec + 1) {
361 copy.remove(dec + 1,
362 1); // typing a delimiter when you are on the delimiter
363 } // should be treated as typing right arrow
364 // too many decimal points
365 if (copy.size() - dec > QDoubleSpinBox::decimals() + 1) {
366 QSBDEBUG() << __FILE__ << __LINE__ << "state is set to Invalid";
367 state = QValidator::Invalid;
368 goto end;
369 }
370 // after decimal separator no thousand char
371 for (int i = dec + 1; i < copy.size(); ++i) {
372 if (copy.at(i).isSpace() || copy.at(i) == thousand) {
373 QSBDEBUG() << __FILE__ << __LINE__ << "state is set to Invalid";
374 state = QValidator::Invalid;
375 goto end;
376 }
377 }
378 // if no decimal separator exists
379 } else {
380 const QChar &last = copy.at(len - 1);
381 const QChar &secondLast = copy.at(len - 2);
382 // group of two thousand or space chars is invalid
383 if ((last == thousand || last.isSpace()) && (secondLast == thousand || secondLast.isSpace())) {
384 state = QValidator::Invalid;
385 QSBDEBUG() << __FILE__ << __LINE__ << "state is set to Invalid";
386 goto end;
387 }
388 // two space chars is invalid
389 else if (last.isSpace() && (!thousand.isSpace() || secondLast.isSpace())) {
390 state = QValidator::Invalid;
391 QSBDEBUG() << __FILE__ << __LINE__ << "state is set to Invalid";
392 goto end;
393 }
394 }
395 } // end if (len > 1)
396
397 // block of remaining test before 'end' mark
398 {
399 bool ok = false;
400 bool notAcceptable = false;
401
402 // convert 'copy' to double, and check if that was 'ok'
403 QLocale loc(locale());
404 num = loc.toDouble(copy, &ok);
405 QSBDEBUG() << __FILE__ << __LINE__ << loc << copy << num << ok;
406
407 // conversion to double did fail
408 if (!ok) {
409 // maybe thousand char was responsable
410 if (thousand.isPrint()) {
411 // if no thousand sign is possible, then
412 // something else is responable -> Invalid
413 if (max < 1000 && min > -1000 && copy.contains(thousand)) {
414 state = QValidator::Invalid;
415 QSBDEBUG() << __FILE__ << __LINE__ << "state is set to Invalid";
416 goto end;
417 }
418
419 // two thousand-chars after one other are not valid
420 const int copyLen = copy.size();
421 for (int i = 0; i < copyLen - 1; ++i) {
422 if (copy.at(i) == thousand && copy.at(i + 1) == thousand) {
423 QSBDEBUG() << __FILE__ << __LINE__ << "state is set to Invalid";
424 state = QValidator::Invalid;
425 goto end;
426 }
427 }
428
429 // remove thousand-chars
430 const int s = copy.size();
431 copy.remove(thousand);
432 pos = qMax(0, pos - (s - copy.size()));
433
434 num = loc.toDouble(copy, &ok);
435 QSBDEBUG() << thousand << num << copy << ok;
436
437 // if conversion still not valid, then reason unknown -> Invalid
438 if (!ok) {
439 state = QValidator::Invalid;
440 QSBDEBUG() << __FILE__ << __LINE__ << "state is set to Invalid";
441 goto end;
442 }
443 notAcceptable = true; // -> state = Intermediate
444 } // endif: (thousand.isPrint())
445 }
446
447 // no thousand sign, but still invalid for unknown reason
448 if (!ok) {
449 state = QValidator::Invalid;
450 QSBDEBUG() << __FILE__ << __LINE__ << "state is set to Invalid";
451 }
452 // number valid and within valid range
453 else if (num >= min && num <= max) {
454 if (notAcceptable) {
455 state = QValidator::Intermediate; // conversion to num initially failed
456 } else {
457 state = QValidator::Acceptable;
458 }
459 QSBDEBUG() << __FILE__ << __LINE__ << "state is set to "
460 << (state == QValidator::Intermediate ? "Intermediate" : "Acceptable");
461 }
462 // when max and min is the same the only non-Invalid input is max (or min)
463 else if (max == min) {
464 state = QValidator::Invalid;
465 QSBDEBUG() << __FILE__ << __LINE__ << "state is set to Invalid";
466 } else {
467 // value out of valid range (coves only special cases)
468 if ((num >= 0 && num > max) || (num < 0 && num < min)) {
469 state = QValidator::Invalid;
470 QSBDEBUG() << __FILE__ << __LINE__ << "state is set to Invalid";
471 } else {
472 // invalid range, further test with 'isIntermediateValue'
473 if (isIntermediateValue(copy)) {
474 state = QValidator::Intermediate;
475 } else {
476 state = QValidator::Invalid;
477 }
478 QSBDEBUG() << __FILE__ << __LINE__ << "state is set to "
479 << (state == QValidator::Intermediate ? "Intermediate" : "Acceptable");
480 }
481 }
482 }
483
484end:
485 // if something went wrong, set num to something valid
486 if (state != QValidator::Acceptable) {
487 num = max > 0 ? min : max;
488 }
489
490 // save (private) cache values
491 cachedText = prefix() + copy + suffix();
492 cachedState = state;
493 cachedValue = QVariant(num);
494 // return resulting valid num
495 return QVariant(num);
496}
497
502// reimplemented function, copied from QAbstractSpinBoxPrivate::stripped
503QString QScienceSpinBox::stripped(const QString &t, int *pos) const {
504 QString text = t;
505 QString prefixtext = prefix();
506 QString suffixtext = suffix();
507
508 if (specialValueText().size() == 0 || text != specialValueText()) {
509 int from = 0;
510 int size = text.size();
511 bool changed = false;
512 if (prefixtext.size() && text.startsWith(prefixtext)) {
513 from += prefixtext.size();
514 size -= from;
515 changed = true;
516 }
517 if (suffixtext.size() && text.endsWith(suffixtext)) {
518 size -= suffixtext.size();
519 changed = true;
520 }
521 if (changed)
522 text = text.mid(from, size);
523 }
524
525 const int s = text.size();
526 text = text.trimmed();
527 if (pos)
528 (*pos) -= (s - text.size());
529 return text;
530}
531
532} // namespace API
533} // namespace MantidQt
gsl_vector * tmp
double value
The value of the point.
Definition: FitMW.cpp:51
double left
Definition: LineProfile.cpp:80
double right
Definition: LineProfile.cpp:81
#define QSBDEBUG
QString stripped(const QString &t, int *pos) const
bool isIntermediateValue(const QString &str) const
double valueFromText(const QString &text) const override
void initLocalValues(QWidget *parent)
QString textFromValue(double value) const override
text to be displayed in spinbox
QScienceSpinBox(QWidget *parent=nullptr)
QValidator::State validate(QString &text, int &pos) const override
bool m_logSteps
Will step in a log way (multiplicatively)
double round(double value) const
Round.
void stepBy(int steps) override
void fixup(QString &input) const override
QVariant validateAndInterpret(QString &input, int &pos, QValidator::State &state) const
bool isIntermediateValueHelper(qint64 num, qint64 min, qint64 max, qint64 *match=nullptr)
The AlgorithmProgressDialogPresenter keeps track of the running algorithms and displays a progress ba...