From 3c754da0cbc86e06ade2543baecbca25701b7160 Mon Sep 17 00:00:00 2001 From: wmayer Date: Mon, 19 Jan 2015 00:36:07 +0100 Subject: [PATCH] + improve QuantitySpinBox --- src/Gui/PrefWidgets.cpp | 105 +++++---------- src/Gui/PrefWidgets.h | 6 +- src/Gui/QuantitySpinBox.cpp | 250 +++++++++++++++++++++++++++--------- src/Gui/QuantitySpinBox.h | 4 +- 4 files changed, 219 insertions(+), 146 deletions(-) diff --git a/src/Gui/PrefWidgets.cpp b/src/Gui/PrefWidgets.cpp index ee34d7ef8..3404edefd 100644 --- a/src/Gui/PrefWidgets.cpp +++ b/src/Gui/PrefWidgets.cpp @@ -459,8 +459,7 @@ class PrefQuantitySpinBoxPrivate { public: PrefQuantitySpinBoxPrivate() : - historySize(5), - saveSize(5) + historySize(5) { } ~PrefQuantitySpinBoxPrivate() @@ -470,7 +469,6 @@ public: QByteArray prefGrp; ParameterGrp::handle handle; int historySize; - int saveSize; }; } @@ -485,6 +483,8 @@ PrefQuantitySpinBox::~PrefQuantitySpinBox() void PrefQuantitySpinBox::contextMenuEvent(QContextMenuEvent *event) { + Q_D(PrefQuantitySpinBox); + QMenu *editMenu = lineEdit()->createStandardContextMenu(); editMenu->setTitle(tr("Edit")); QMenu* menu = new QMenu(QString::fromAscii("PrefQuantitySpinBox")); @@ -497,9 +497,9 @@ void PrefQuantitySpinBox::contextMenuEvent(QContextMenuEvent *event) std::vector actions; // add the history menu part... - std::vector history = getHistory(); + QStringList history = getHistory(); - for (std::vector::const_iterator it = history.begin();it!= history.end();++it) { + for (QStringList::const_iterator it = history.begin();it!= history.end();++it) { actions.push_back(menu->addAction(*it)); values.push_back(*it); } @@ -507,24 +507,23 @@ void PrefQuantitySpinBox::contextMenuEvent(QContextMenuEvent *event) // add the save value portion of the menu menu->addSeparator(); QAction *saveValueAction = menu->addAction(tr("Save value")); - std::vector savedValues = getSavedValues(); - - for (std::vector::const_iterator it = savedValues.begin();it!= savedValues.end();++it) { - actions.push_back(menu->addAction(*it)); - values.push_back(*it); - } + QAction *clearListAction = menu->addAction(tr("Clear list")); + clearListAction->setDisabled(history.empty()); // call the menu and wait until its back - QAction *saveAction = menu->exec(event->globalPos()); + QAction *userAction = menu->exec(event->globalPos()); // look what the user has choosen - if (saveAction == saveValueAction) { - pushToSavedValues(this->text()); + if (userAction == saveValueAction) { + pushToHistory(this->text()); + } + else if (userAction == clearListAction) { + d->handle->Clear(); } else { int i=0; for (std::vector::const_iterator it = actions.begin();it!=actions.end();++it,i++) { - if (*it == saveAction) { + if (*it == userAction) { lineEdit()->setText(values[i]); break; } @@ -544,32 +543,29 @@ void PrefQuantitySpinBox::pushToHistory(const QString &valueq) else val = valueq; - // check if already in: - std::vector hist = getHistory(); - for (std::vector::const_iterator it = hist.begin();it!=hist.end();++it) { - if (*it == val) - return; - } - std::string value(val.toUtf8()); if (d->handle.isValid()) { - for (int i = d->historySize -1 ; i>=0 ;i--) { - QByteArray hist1 = "Hist"; - QByteArray hist0 = "Hist"; - hist1.append(QByteArray::number(i+1)); - hist0.append(QByteArray::number(i)); - std::string tHist = d->handle->GetASCII(hist0); - if (!tHist.empty()) - d->handle->SetASCII(hist1,tHist.c_str()); + // do nothing if the given value is on top of the history + std::string tHist = d->handle->GetASCII("Hist0"); + if (tHist != val.toUtf8().constData()) { + for (int i = d->historySize -1 ; i>=0 ;i--) { + QByteArray hist1 = "Hist"; + QByteArray hist0 = "Hist"; + hist1.append(QByteArray::number(i+1)); + hist0.append(QByteArray::number(i)); + std::string tHist = d->handle->GetASCII(hist0); + if (!tHist.empty()) + d->handle->SetASCII(hist1,tHist.c_str()); + } + d->handle->SetASCII("Hist0",value.c_str()); } - d->handle->SetASCII("Hist0",value.c_str()); } } -std::vector PrefQuantitySpinBox::getHistory() const +QStringList PrefQuantitySpinBox::getHistory() const { Q_D(const PrefQuantitySpinBox); - std::vector res; + QStringList res; if (d->handle.isValid()) { std::string tmp; @@ -583,56 +579,17 @@ std::vector PrefQuantitySpinBox::getHistory() const break; // end of history reached } } + return res; } void PrefQuantitySpinBox::setToLastUsedValue() { - std::vector hist = getHistory(); + QStringList hist = getHistory(); if (!hist.empty()) lineEdit()->setText(hist[0]); } -void PrefQuantitySpinBox::pushToSavedValues(const QString &valueq) -{ - Q_D(PrefQuantitySpinBox); - std::string value; - value = valueq.toUtf8().constData(); - - if (d->handle.isValid()) { - for (int i = d->saveSize -1 ; i>=0 ;i--) { - QByteArray hist1 = "Save"; - QByteArray hist0 = "Save"; - hist1.append(QByteArray::number(i+1)); - hist0.append(QByteArray::number(i)); - std::string tHist = d->handle->GetASCII(hist0); - if (!tHist.empty()) - d->handle->SetASCII(hist1,tHist.c_str()); - } - d->handle->SetASCII("Save0",value.c_str()); - } -} - -std::vector PrefQuantitySpinBox::getSavedValues() const -{ - Q_D(const PrefQuantitySpinBox); - std::vector res; - - if (d->handle.isValid()) { - std::string tmp; - for (int i = 0 ; i< d->saveSize ;i++) { - QByteArray hist = "Save"; - hist.append(QByteArray::number(i)); - tmp = d->handle->GetASCII(hist); - if (!tmp.empty()) - res.push_back(QString::fromUtf8(tmp.c_str())); - else - break; // end of history reached - } - } - return res; -} - void PrefQuantitySpinBox::setParamGrpPath(const QByteArray& path) { Q_D(PrefQuantitySpinBox); diff --git a/src/Gui/PrefWidgets.h b/src/Gui/PrefWidgets.h index 5217f37fb..333224d3b 100644 --- a/src/Gui/PrefWidgets.h +++ b/src/Gui/PrefWidgets.h @@ -306,11 +306,7 @@ public: /// push a new value to the history, if no string given the actual text of the input field is used. void pushToHistory(const QString& value = QString()); /// get the history of the field, newest first - std::vector getHistory() const; - /// push a new value to the history, if no string given the actual text of the input field is used. - void pushToSavedValues(const QString& value); - /// get the history of the field, newest first - std::vector getSavedValues() const; + QStringList getHistory() const; //@} protected: diff --git a/src/Gui/QuantitySpinBox.cpp b/src/Gui/QuantitySpinBox.cpp index 51a0e8fa7..45c74fc0e 100644 --- a/src/Gui/QuantitySpinBox.cpp +++ b/src/Gui/QuantitySpinBox.cpp @@ -48,38 +48,158 @@ public: { } + QString stripped(const QString &t, int *pos) const + { + QString text = t; + const int s = text.size(); + text = text.trimmed(); + if (pos) + (*pos) -= (s - text.size()); + return text; + } + Base::Quantity validateAndInterpret(QString& input, int& pos, QValidator::State& state) const { Base::Quantity res; - if (input.isEmpty()) { - state = QValidator::Intermediate; - return res; + const double max = this->maximum; + const double min = this->minimum; + + QString copy = input; + + int len = copy.size(); + const bool plus = max >= 0; + const bool minus = min <= 0; + + switch (len) { + case 0: + state = max != min ? QValidator::Intermediate : QValidator::Invalid; + goto end; + case 1: + if (copy.at(0) == locale.decimalPoint()) { + state = QValidator::Intermediate; + copy.prepend(QLatin1Char('0')); + pos++; + len++; + goto end; + } + else if (copy.at(0) == QLatin1Char('+')) { + // the quantity parser doesn't allow numbers of the form '+1.0' + state = QValidator::Invalid; + goto end; + } + else if (copy.at(0) == QLatin1Char('-')) { + if (minus) + state = QValidator::Intermediate; + else + state = QValidator::Invalid; + goto end; + } + break; + case 2: + if (copy.at(1) == locale.decimalPoint() + && (plus && copy.at(0) == QLatin1Char('+'))) { + state = QValidator::Intermediate; + goto end; + } + if (copy.at(1) == locale.decimalPoint() + && (minus && copy.at(0) == QLatin1Char('-'))) { + state = QValidator::Intermediate; + copy.insert(1, QLatin1Char('0')); + pos++; + len++; + goto end; + } + break; + default: break; } + if (copy.at(0) == locale.groupSeparator()) { + state = QValidator::Invalid; + goto end; + } + else if (len > 1) { + const int dec = copy.indexOf(locale.decimalPoint()); + if (dec != -1) { + if (dec + 1 < copy.size() && copy.at(dec + 1) == locale.decimalPoint() && pos == dec + 1) { + copy.remove(dec + 1, 1); + } + else if (copy.indexOf(locale.decimalPoint(), dec + 1) != -1) { + // trying to add a second decimal point is not allowed + state = QValidator::Invalid; + goto end; + } + } + for (int i=dec + 1; i this->maximum || value < this->minimum) - state = QValidator::Invalid; - else - state = QValidator::Acceptable; + res = Base::Quantity::parse(copy2); + value = res.getValue(); + ok = true; } catch (Base::Exception&) { - // Actually invalid input but the newInput slot gives - // some feedback - state = QValidator::Intermediate; } + if (!ok) { + // input may not be finished + state = QValidator::Intermediate; + } + else if (value >= min && value <= max) { + if (copy.endsWith(locale.decimalPoint())) { + // input shouldn't end with a decimal point + state = QValidator::Intermediate; + } + else if (res.getUnit().isEmpty() && !this->unit.isEmpty()) { + // if not dimensionless the input should have a dimension + state = QValidator::Intermediate; + } + else if (res.getUnit() != this->unit) { + state = QValidator::Invalid; + } + else { + state = QValidator::Acceptable; + } + } + else if (max == min) { // when max and min is the same the only non-Invalid input is max (or min) + state = QValidator::Invalid; + } + else { + if ((value >= 0 && value > max) || (value < 0 && value < min)) { + state = QValidator::Invalid; + } + else { + state = QValidator::Intermediate; + } + } +end: + if (state != QValidator::Acceptable) { + res.setValue(max > 0 ? min : max); + } + + input = copy; return res; } + QLocale locale; bool validInput; - QString errorText; + QString validStr; Base::Quantity quantity; Base::Unit unit; double unitValue; @@ -93,6 +213,7 @@ public: QuantitySpinBox::QuantitySpinBox(QWidget *parent) : QAbstractSpinBox(parent), d_ptr(new QuantitySpinBoxPrivate()) { + d_ptr->locale = locale(); this->setContextMenuPolicy(Qt::DefaultContextMenu); QObject::connect(lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(userInput(QString))); @@ -145,54 +266,42 @@ bool QuantitySpinBox::hasValidInput() const return d->validInput; } +// Gets called after call of 'validateAndInterpret' void QuantitySpinBox::userInput(const QString & text) { Q_D(QuantitySpinBox); - if (text.isEmpty()) { - d->errorText.clear(); + QString tmp = text; + int pos; + QValidator::State state; + Base::Quantity res = d->validateAndInterpret(tmp, pos, state); + if (state == QValidator::Acceptable) { d->validInput = true; - return; + d->validStr = text; } - - Base::Quantity res; - try { - QString input = text; - fixup(input); - res = Base::Quantity::parse(input); + else if (state == QValidator::Intermediate) { + tmp = tmp.trimmed(); + tmp += QLatin1Char(' '); + tmp += d->unitStr; + Base::Quantity res2 = d->validateAndInterpret(tmp, pos, state); + if (state == QValidator::Acceptable) { + d->validInput = true; + d->validStr = tmp; + res = res2; + } + else { + d->validInput = false; + return; + } } - catch (Base::Exception &e) { - d->errorText = QString::fromAscii(e.what()); - parseError(d->errorText); + else { d->validInput = false; return; } - if (res.getUnit().isEmpty()) - res.setUnit(d->unit); - - // check if unit fits! - if (!d->unit.isEmpty() && !res.getUnit().isEmpty() && d->unit != res.getUnit()){ - parseError(QString::fromAscii("Wrong unit")); - d->validInput = false; - return; - } - - d->errorText.clear(); - d->validInput = true; - - if (res.getValue() > d->maximum){ - res.setValue(d->maximum); - d->errorText = tr("Maximum reached"); - } - if (res.getValue() < d->minimum){ - res.setValue(d->minimum); - d->errorText = tr("Minimum reached"); - } - - double dFactor; - res.getUserString(dFactor,d->unitStr); - d->unitValue = res.getValue()/dFactor; + double factor; + res.getUserString(factor,d->unitStr); + d->unitValue = res.getValue()/factor; d->quantity = res; // signaling @@ -276,7 +385,7 @@ void QuantitySpinBox::setRange(double minimum, double maximum) QAbstractSpinBox::StepEnabled QuantitySpinBox::stepEnabled() const { Q_D(const QuantitySpinBox); - if (isReadOnly() || !d->validInput) + if (isReadOnly()/* || !d->validInput*/) return StepNone; if (wrapping()) return StepEnabled(StepUpEnabled | StepDownEnabled); @@ -331,6 +440,20 @@ void QuantitySpinBox::focusInEvent(QFocusEvent * event) } } +void QuantitySpinBox::focusOutEvent(QFocusEvent * event) +{ + Q_D(QuantitySpinBox); + + int pos; + QString text = lineEdit()->text(); + QValidator::State state; + d->validateAndInterpret(text, pos, state); + if (state != QValidator::Acceptable) { + lineEdit()->setText(d->validStr); + } + QAbstractSpinBox::focusOutEvent(event); +} + void QuantitySpinBox::clear() { QAbstractSpinBox::clear(); @@ -377,10 +500,15 @@ Base::Quantity QuantitySpinBox::valueFromText(const QString &text) const Q_D(const QuantitySpinBox); QString copy = text; - fixup( copy ); int pos = lineEdit()->cursorPosition(); QValidator::State state = QValidator::Acceptable; - return d->validateAndInterpret(copy, pos, state); + Base::Quantity quant = d->validateAndInterpret(copy, pos, state); + if (state != QValidator::Acceptable) { + fixup(copy); + quant = d->validateAndInterpret(copy, pos, state); + } + + return quant; } QValidator::State QuantitySpinBox::validate(QString &text, int &pos) const @@ -388,19 +516,13 @@ QValidator::State QuantitySpinBox::validate(QString &text, int &pos) const Q_D(const QuantitySpinBox); QValidator::State state; - QString copy = text; - fixup(copy); - d->validateAndInterpret(copy, pos, state); + d->validateAndInterpret(text, pos, state); return state; } void QuantitySpinBox::fixup(QString &input) const { input.remove(locale().groupSeparator()); - if (locale().negativeSign() != QLatin1Char('-')) - input.replace(locale().negativeSign(), QLatin1Char('-')); - if (locale().positiveSign() != QLatin1Char('+')) - input.replace(locale().positiveSign(), QLatin1Char('+')); } diff --git a/src/Gui/QuantitySpinBox.h b/src/Gui/QuantitySpinBox.h index e7c6fe58a..c904e1059 100644 --- a/src/Gui/QuantitySpinBox.h +++ b/src/Gui/QuantitySpinBox.h @@ -108,6 +108,7 @@ protected: virtual StepEnabled stepEnabled() const; virtual void showEvent(QShowEvent * event); virtual void focusInEvent(QFocusEvent * event); + virtual void focusOutEvent(QFocusEvent * event); private: void updateText(const Base::Quantity&); @@ -124,9 +125,6 @@ Q_SIGNALS: */ void valueChanged(double); - /// Signal for an invalid user input - void parseError(const QString& errorText); - private: QScopedPointer d_ptr; Q_DISABLE_COPY(QuantitySpinBox)