1
0
mirror of https://bitbucket.org/librepilot/librepilot.git synced 2025-02-20 10:54:14 +01:00

GCS: UAVObjectBrowser: Use new widget for editing floats that supports scientific notation

git-svn-id: svn://svn.openpilot.org/OpenPilot/trunk@2325 ebee16cc-31ac-478f-84a7-5cbb03baadba
This commit is contained in:
corvus 2011-01-03 15:29:02 +00:00 committed by corvus
parent b826d4e51f
commit 5b9a50127f
7 changed files with 612 additions and 4 deletions

View File

@ -2,6 +2,7 @@ TEMPLATE = subdirs
CONFIG += ordered
SUBDIRS = \
qscispinbox\
qtconcurrent \
aggregation \
extensionsystem \

View File

@ -0,0 +1,544 @@
/**
* Code copied from http://www.matthiaspospiech.de/blog/2009/01/03/qt-spinbox-widget-with-scientific-notation/
*/
#include "QScienceSpinBox.h"
#include <limits>
//#define QSPINBOX_QSBDEBUG
#ifdef QSPINBOX_QSBDEBUG
# define QSBDEBUG qDebug
#else
# define QSBDEBUG if (false) qDebug
#endif
QScienceSpinBox::QScienceSpinBox(QWidget * parent)
: QDoubleSpinBox(parent)
{
initLocalValues(parent);
setDecimals(8);
QDoubleSpinBox::setDecimals(1000);
// set Range to maximum possible values
double doubleMax = std::numeric_limits<double>::max();
setRange(-doubleMax, doubleMax);
v = new QDoubleValidator(this);
v->setDecimals(1000); // (standard anyway)
v->setNotation(QDoubleValidator::ScientificNotation);
this->lineEdit()->setValidator(v);
}
void QScienceSpinBox::initLocalValues(QWidget *parent)
{
const QString str = (parent ? parent->locale() : QLocale()).toString(4567.1);
if (str.size() == 6) {
delimiter = str.at(4);
thousand = QChar((ushort)0);
} else if (str.size() == 7) {
thousand = str.at(1);
delimiter = str.at(5);
}
Q_ASSERT(!delimiter.isNull());
}
int QScienceSpinBox::decimals() const
{
return dispDecimals;
}
void QScienceSpinBox::setDecimals(int value)
{
dispDecimals = value;
}
// overwritten virtual function of QAbstractSpinBox
void QScienceSpinBox::stepBy(int steps)
{
if (steps < 0)
stepDown();
else
stepUp();
}
void QScienceSpinBox::stepDown()
{
QSBDEBUG() << "stepDown()";
setValue(value()/10.0);
}
void QScienceSpinBox::stepUp()
{
QSBDEBUG() << "stepUp()";
setValue(value()*10.0);
}
/*!
* text to be displayed in spinbox
*/
QString QScienceSpinBox::textFromValue(double value) const
{
// convert to string -> Using exponetial display with internal decimals
QString str = locale().toString(value, 'e', dispDecimals);
// remove thousand sign
if (qAbs(value) >= 1000.0) {
str.remove(thousand);
}
return str;
}
double QScienceSpinBox::valueFromText(const QString &text) const
{
QString copy = text;
int pos = this->lineEdit()->cursorPosition();
QValidator::State state = QValidator::Acceptable;
return validateAndInterpret(copy, pos, state).toDouble();
}
// this function is never used...?
double QScienceSpinBox::round(double value) const
{
const QString strDbl = locale().toString(value, 'g', dispDecimals);
return locale().toDouble(strDbl);
}
// overwritten virtual function of QAbstractSpinBox
QValidator::State QScienceSpinBox::validate(QString &text, int &pos) const
{
QValidator::State state;
validateAndInterpret(text, pos, state);
return state;
}
// overwritten virtual function of QAbstractSpinBox
void QScienceSpinBox::fixup(QString &input) const
{
input.remove(thousand);
}
// reimplemented function, copied from QDoubleSpinBoxPrivate::isIntermediateValue
bool QScienceSpinBox::isIntermediateValue(const QString &str) const
{
QSBDEBUG() << "input is" << str << minimum() << maximum();
qint64 dec = 1;
for (int i=0; i < decimals(); ++i)
dec *= 10;
const QLatin1Char dot('.');
/*!
* determine minimum possible values on left and right of Decimal-char
*/
// I know QString::number() uses CLocale so I use dot
const QString minstr = QString::number(minimum(), 'f', QDoubleSpinBox::decimals());
qint64 min_left = minstr.left(minstr.indexOf(dot)).toLongLong();
qint64 min_right = minstr.mid(minstr.indexOf(dot) + 1).toLongLong();
const QString maxstr = QString::number(maximum(), 'f', QDoubleSpinBox::decimals());
qint64 max_left = maxstr.left(maxstr.indexOf(dot)).toLongLong();
qint64 max_right = maxstr.mid(maxstr.indexOf(dot) + 1).toLongLong();
/*!
* determine left and right long values (left and right of delimiter)
*/
const int dotindex = str.indexOf(delimiter);
const bool negative = maximum() < 0;
qint64 left = 0, right = 0;
bool doleft = true;
bool doright = true;
// no separator -> everthing in left
if (dotindex == -1) {
left = str.toLongLong();
doright = false;
}
// separator on left or contains '+'
else if (dotindex == 0 || (dotindex == 1 && str.at(0) == QLatin1Char('+'))) {
// '+' at negative max
if (negative) {
QSBDEBUG() << __FILE__ << __LINE__ << "returns false";
return false;
}
doleft = false;
right = str.mid(dotindex + 1).toLongLong();
}
// contains '-'
else if (dotindex == 1 && str.at(0) == QLatin1Char('-')) {
// '-' at positiv max
if (!negative) {
QSBDEBUG() << __FILE__ << __LINE__ << "returns false";
return false;
}
doleft = false;
right = str.mid(dotindex + 1).toLongLong();
} else {
left = str.left(dotindex).toLongLong();
if (dotindex == str.size() - 1) { // nothing right of Separator
doright = false;
} else {
right = str.mid(dotindex + 1).toLongLong();
}
}
// left > 0, with max < 0 and no '-'
if ((left >= 0 && max_left < 0 && !str.startsWith(QLatin1Char('-')))
// left > 0, with min > 0
|| (left < 0 && min_left >= 0))
{
QSBDEBUG("returns false");
return false;
}
qint64 match = min_left;
if (doleft && !isIntermediateValueHelper(left, min_left, max_left, &match)) {
QSBDEBUG() << __FILE__ << __LINE__ << "returns false";
return false;
}
if (doright) {
QSBDEBUG("match %lld min_left %lld max_left %lld", match, min_left, max_left);
if (!doleft) {
if (min_left == max_left) {
const bool ret = isIntermediateValueHelper(qAbs(left),
negative ? max_right : min_right,
negative ? min_right : max_right);
QSBDEBUG() << __FILE__ << __LINE__ << "returns" << ret;
return ret;
} else if (qAbs(max_left - min_left) == 1) {
const bool ret = isIntermediateValueHelper(qAbs(left), min_right, negative ? 0 : dec)
|| isIntermediateValueHelper(qAbs(left), negative ? dec : 0, max_right);
QSBDEBUG() << __FILE__ << __LINE__ << "returns" << ret;
return ret;
} else {
const bool ret = isIntermediateValueHelper(qAbs(left), 0, dec);
QSBDEBUG() << __FILE__ << __LINE__ << "returns" << ret;
return ret;
}
}
if (match != min_left) {
min_right = negative ? dec : 0;
}
if (match != max_left) {
max_right = negative ? 0 : dec;
}
qint64 tmpl = negative ? max_right : min_right;
qint64 tmpr = negative ? min_right : max_right;
const bool ret = isIntermediateValueHelper(right, tmpl, tmpr);
QSBDEBUG() << __FILE__ << __LINE__ << "returns" << ret;
return ret;
}
QSBDEBUG() << __FILE__ << __LINE__ << "returns true";
return true;
}
/*!
\internal Multi purpose function that parses input, sets state to
the appropriate state and returns the value it will be interpreted
as.
*/
// reimplemented function, copied from QDoubleSpinBoxPrivate::validateAndInterpret
QVariant QScienceSpinBox::validateAndInterpret(
QString &input,
int &pos,
QValidator::State &state) const
{
/*! return 'cachedText' if
* input = cachedText, or input Empty
*/
static QString cachedText;
static QValidator::State cachedState;
static QVariant cachedValue;
if (cachedText == input && !input.isEmpty()) {
state = cachedState;
QSBDEBUG() << "cachedText was" << "'" << cachedText << "'" << "state was "
<< state << " and value was " << cachedValue;
return cachedValue;
}
const double max = maximum();
const double min = minimum();
// removes prefix & suffix
QString copy = stripped(input, &pos);
QSBDEBUG() << "input" << input << "copy" << copy;
int len = copy.size();
double num = min;
const bool plus = max >= 0;
const bool minus = min <= 0;
// Test possible 'Intermediate' reasons
switch (len)
{
case 0:
// Length 0 is always 'Intermediate', except for min=max
if (max != min) {
state = QValidator::Intermediate;
} else {
state = QValidator::Invalid;
}
goto end;
case 1:
// if only char is '+' or '-'
if (copy.at(0) == delimiter
|| (plus && copy.at(0) == QLatin1Char('+'))
|| (minus && copy.at(0) == QLatin1Char('-'))) {
state = QValidator::Intermediate;
goto end;
}
break;
case 2:
// if only chars are '+' or '-' followed by Comma seperator (delimiter)
if (copy.at(1) == delimiter
&& ((plus && copy.at(0) == QLatin1Char('+')) || (minus && copy.at(0) == QLatin1Char('-')))) {
state = QValidator::Intermediate;
goto end;
}
break;
default: break;
} // end switch
// First char must not be thousand-char
if (copy.at(0) == thousand)
{
QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid";
state = QValidator::Invalid;
goto end;
}
// Test possible 'Invalid' reasons
else if (len > 1)
{
const int dec = copy.indexOf(delimiter); // position of delimiter
// if decimal separator (delimiter) exists
if (dec != -1) {
// not two delimiters after one other (meaning something like ',,')
if (dec + 1 < copy.size() && copy.at(dec + 1) == delimiter && pos == dec + 1) {
copy.remove(dec + 1, 1); // typing a delimiter when you are on the delimiter
} // should be treated as typing right arrow
// too many decimal points
if (copy.size() - dec > QDoubleSpinBox::decimals() + 1) {
QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid";
state = QValidator::Invalid;
goto end;
}
// after decimal separator no thousand char
for (int i=dec + 1; i<copy.size(); ++i) {
if (copy.at(i).isSpace() || copy.at(i) == thousand) {
QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid";
state = QValidator::Invalid;
goto end;
}
}
// if no decimal separator exists
} else {
const QChar &last = copy.at(len - 1);
const QChar &secondLast = copy.at(len - 2);
// group of two thousand or space chars is invalid
if ((last == thousand || last.isSpace())
&& (secondLast == thousand || secondLast.isSpace())) {
state = QValidator::Invalid;
QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid";
goto end;
}
// two space chars is invalid
else if (last.isSpace() && (!thousand.isSpace() || secondLast.isSpace())) {
state = QValidator::Invalid;
QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid";
goto end;
}
}
} // end if (len > 1)
// block of remaining test before 'end' mark
{
bool ok = false;
bool notAcceptable = false;
// convert 'copy' to double, and check if that was 'ok'
QLocale loc(locale());
num = loc.toDouble(copy, &ok);
QSBDEBUG() << __FILE__ << __LINE__ << loc << copy << num << ok;
// conversion to double did fail
if (!ok) {
// maybe thousand char was responsable
if (thousand.isPrint())
{
// if no thousand sign is possible, then
// something else is responable -> Invalid
if (max < 1000 && min > -1000 && copy.contains(thousand)) {
state = QValidator::Invalid;
QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid";
goto end;
}
// two thousand-chars after one other are not valid
const int len = copy.size();
for (int i=0; i<len- 1; ++i) {
if (copy.at(i) == thousand && copy.at(i + 1) == thousand) {
QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid";
state = QValidator::Invalid;
goto end;
}
}
// remove thousand-chars
const int s = copy.size();
copy.remove(thousand);
pos = qMax(0, pos - (s - copy.size()));
num = loc.toDouble(copy, &ok);
QSBDEBUG() << thousand << num << copy << ok;
// if conversion still not valid, then reason unknown -> Invalid
if (!ok) {
state = QValidator::Invalid;
QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid";
goto end;
}
notAcceptable = true; // -> state = Intermediate
} // endif: (thousand.isPrint())
}
// no thousand sign, but still invalid for unknown reason
if (!ok) {
state = QValidator::Invalid;
QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid";
}
// number valid and within valid range
else if (num >= min && num <= max) {
if (notAcceptable) {
state = QValidator::Intermediate; // conversion to num initially failed
} else {
state = QValidator::Acceptable;
}
QSBDEBUG() << __FILE__ << __LINE__<< "state is set to "
<< (state == QValidator::Intermediate ? "Intermediate" : "Acceptable");
}
// when max and min is the same the only non-Invalid input is max (or min)
else if (max == min) {
state = QValidator::Invalid;
QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid";
} else {
// value out of valid range (coves only special cases)
if ((num >= 0 && num > max) || (num < 0 && num < min)) {
state = QValidator::Invalid;
QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid";
} else {
// invalid range, further test with 'isIntermediateValue'
if (isIntermediateValue(copy)) {
state = QValidator::Intermediate;
} else {
state = QValidator::Invalid;
}
QSBDEBUG() << __FILE__ << __LINE__<< "state is set to "
<< (state == QValidator::Intermediate ? "Intermediate" : "Acceptable");
}
}
}
end:
// if something went wrong, set num to something valid
if (state != QValidator::Acceptable) {
num = max > 0 ? min : max;
}
// save (private) cache values
cachedText = prefix() + copy + suffix();
cachedState = state;
cachedValue = QVariant(num);
// return resulting valid num
return QVariant(num);
}
/*!
\internal
Strips any prefix/suffix from \a text.
*/
// reimplemented function, copied from QAbstractSpinBoxPrivate::stripped
QString QScienceSpinBox::stripped(const QString &t, int *pos) const
{
QString text = t;
QString prefixtext = prefix();
QString suffixtext = suffix();
if (specialValueText().size() == 0 || text != specialValueText()) {
int from = 0;
int size = text.size();
bool changed = false;
if (prefixtext.size() && text.startsWith(prefixtext)) {
from += prefixtext.size();
size -= from;
changed = true;
}
if (suffixtext.size() && text.endsWith(suffixtext)) {
size -= suffixtext.size();
changed = true;
}
if (changed)
text = text.mid(from, size);
}
const int s = text.size();
text = text.trimmed();
if (pos)
(*pos) -= (s - text.size());
return text;
}
// reimplemented function, copied from qspinbox.cpp
static bool isIntermediateValueHelper(qint64 num, qint64 min, qint64 max, qint64 *match)
{
QSBDEBUG("%lld %lld %lld", num, min, max);
if (num >= min && num <= max) {
if (match)
*match = num;
QSBDEBUG("returns true 0");
return true;
}
qint64 tmp = num;
int numDigits = 0;
int digits[10];
if (tmp == 0) {
numDigits = 1;
digits[0] = 0;
} else {
tmp = qAbs(num);
for (int i=0; tmp > 0; ++i) {
digits[numDigits++] = tmp % 10;
tmp /= 10;
}
}
int failures = 0;
qint64 number;
for (number=max; number>=min; --number) {
tmp = qAbs(number);
for (int i=0; tmp > 0;) {
if (digits[i] == (tmp % 10)) {
if (++i == numDigits) {
if (match)
*match = number;
QSBDEBUG("returns true 1");
return true;
}
}
tmp /= 10;
}
if (failures++ == 500000) { //upper bound
if (match)
*match = num;
QSBDEBUG("returns true 2");
return true;
}
}
QSBDEBUG("returns false");
return false;
}

View File

@ -0,0 +1,50 @@
/**
* Code copied from http://www.matthiaspospiech.de/blog/2009/01/03/qt-spinbox-widget-with-scientific-notation/
*/
#ifndef __QScienceSpinBox_H__
#define __QScienceSpinBox_H__
#include <QtGui/QDoubleSpinBox>
#include <QtGui/QDoubleValidator>
#include <QtGui/QLineEdit>
#include <QtCore/QVariant>
#include <QtCore/QDebug>
#include <QtCore/QString>
static bool isIntermediateValueHelper(qint64 num, qint64 minimum, qint64 maximum, qint64 *match = 0);
class QScienceSpinBox : public QDoubleSpinBox
{
Q_OBJECT
public:
QScienceSpinBox(QWidget * parent = 0);
int decimals() const;
void setDecimals(int value);
QString textFromValue ( double value ) const;
double valueFromText ( const QString & text ) const;
private:
int dispDecimals;
QChar delimiter, thousand;
QDoubleValidator * v;
private:
void initLocalValues(QWidget *parent);
bool isIntermediateValue(const QString &str) const;
QVariant validateAndInterpret(QString &input, int &pos, QValidator::State &state) const;
QValidator::State validate(QString &text, int &pos) const;
void fixup(QString &input) const;
QString stripped(const QString &t, int *pos) const;
double round(double value) const;
void stepBy(int steps);
public slots:
void stepDown();
void stepUp();
};
#endif

View File

@ -0,0 +1 @@
LIBS *= -l$$qtLibraryTarget(QScienceSpinBox)

View File

@ -0,0 +1,11 @@
TEMPLATE = lib
TARGET = QScienceSpinBox
include(../../openpilotgcslibrary.pri)
DEFINES += QSCIENCESPINBOX
HEADERS = QScienceSpinBox.h
SOURCES = QScienceSpinBox.cpp

View File

@ -32,7 +32,7 @@
#include <QtCore/QStringList>
#include <QtGui/QWidget>
#include <QtGui/QSpinBox>
#include <QtGui/QDoubleSpinBox>
#include <qscispinbox/QScienceSpinBox.h>
#include <QtGui/QComboBox>
#include <limits>
@ -231,7 +231,7 @@ public:
}
}
QWidget *createEditor(QWidget *parent) {
QDoubleSpinBox *editor = new QDoubleSpinBox(parent);
QScienceSpinBox *editor = new QScienceSpinBox(parent);
editor->setDecimals(6);
editor->setMinimum(-std::numeric_limits<float>::max());
editor->setMaximum(std::numeric_limits<float>::max());
@ -239,13 +239,13 @@ public:
}
QVariant getEditorValue(QWidget *editor) {
QDoubleSpinBox *spinBox = static_cast<QDoubleSpinBox*>(editor);
QScienceSpinBox *spinBox = static_cast<QScienceSpinBox*>(editor);
spinBox->interpretText();
return spinBox->value();
}
void setEditorValue(QWidget *editor, QVariant value) {
QDoubleSpinBox *spinBox = static_cast<QDoubleSpinBox*>(editor);
QScienceSpinBox *spinBox = static_cast<QScienceSpinBox*>(editor);
spinBox->setValue(value.toDouble());
}
private:

View File

@ -1,3 +1,4 @@
include(../../plugins/uavobjects/uavobjects.pri)
include(../../plugins/coreplugin/coreplugin.pri)
include(../../libs/utils/utils.pri)
include(../../libs/qscispinbox/qscispinbox.pri)