/*************************************************************************** * Copyright (c) 2014 Werner Mayer * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 51 Franklin Street, * * Fifth Floor, Boston, MA 02110-1301, USA * * * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "QuantitySpinBox.h" #include "QuantitySpinBox_p.h" #include "Command.h" #include "Dialogs/DlgExpressionInput.h" #include "Tools.h" #include "Widgets.h" using namespace Gui; using namespace App; using namespace Base; namespace Gui { class QuantitySpinBoxPrivate { public: QuantitySpinBoxPrivate(QuantitySpinBox* q) : validInput(true) , pendingEmit(false) , normalize(true) , checkRangeInExpression(false) , unitValue(0) , maximum(std::numeric_limits::max()) , minimum(-std::numeric_limits::max()) , singleStep(1.0) , q_ptr(q) {} ~QuantitySpinBoxPrivate() = default; 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; } bool validate(QString& input, Base::Quantity& result, const App::ObjectIdentifier& path) const { Q_Q(const QuantitySpinBox); // Do not accept empty strings because the parser will consider // " unit" as "1 unit" which is not the desired behaviour (see #0004104) if (input.isEmpty()) { return false; } bool success = false; QString tmp = input; auto validateInput = [&](QString& tmp) -> QValidator::State { QValidator::State state; Base::Quantity res = validateAndInterpret(tmp, state, path); res.setFormat(quantity.getFormat()); if (state == QValidator::Acceptable) { success = true; result = res; input = tmp; } return state; }; QValidator::State state = validateInput(tmp); if (state == QValidator::Intermediate && q->hasExpression()) { // Accept the expression as it is but try to add the right unit string success = true; Base::Quantity quantity; double value; if (parseString(input, quantity, value, path)) { quantity.setUnit(unit); result = quantity; // Now translate the quantity into its string representation using the user-defined // unit system input = QString::fromStdString(Base::UnitsApi::schemaTranslate(result)); } } return success; } bool parseString( const QString& str, Base::Quantity& result, double& value, const App::ObjectIdentifier& path ) const { App::ObjectIdentifier pathtmp = path; try { QString copy = str; copy.remove(locale.groupSeparator()); // Expression parser std::shared_ptr expr( ExpressionParser::parse(path.getDocumentObject(), copy.toUtf8().constData()) ); if (expr) { std::unique_ptr res(expr->eval()); NumberExpression* n = freecad_cast(res.get()); if (n) { result = n->getQuantity(); value = result.getValue(); return true; } } } catch (Base::Exception&) { return false; } return false; } Base::Quantity validateAndInterpret( QString& input, QValidator::State& state, const App::ObjectIdentifier& path ) const { Base::Quantity res; const double max = this->maximum; const double min = this->minimum; QString copy = input; double value = min; bool ok = false; QChar plus = QLatin1Char('+'), minus = QLatin1Char('-'); if (locale.negativeSign() != minus) { copy.replace(locale.negativeSign(), minus); } if (locale.positiveSign() != plus) { copy.replace(locale.positiveSign(), plus); } QString reverseUnitStr = unitStr; std::reverse(reverseUnitStr.begin(), reverseUnitStr.end()); // Prep for expression parser // This regex matches chunks between +,-,$,^ accounting for matching parenthesis. QRegularExpression chunkRe( QStringLiteral("(?<=^|[\\+\\-])((\\((?>[^()]|(?2))*\\))|[^\\+\\-\n])*(?=$|[\\+\\-])") ); QRegularExpressionMatchIterator expressionChunk = chunkRe.globalMatch(copy); unsigned int lengthOffset = 0; while (expressionChunk.hasNext()) { QRegularExpressionMatch matchChunk = expressionChunk.next(); QString origionalChunk = matchChunk.captured(0); QString copyChunk = origionalChunk; std::reverse(copyChunk.begin(), copyChunk.end()); // Reused regex patterns static const std::string regexUnits = "sAV|VC|lim|nim|im|hpm|[mf]?bl|°|ged|dar|nog|″|′|rroT[uµm]?|K[uµm]?|A[mkM]?|F[" "pnuµm]?|C|S[uµmkM]?|zH[kMGT]?|H[nuµm]?|mhO[kM]?|J[mk]?|Ve[kM]?|V[mk]?|hWk|sW|" "lack?|N[mkM]?|g[uµmk]?|lm?|(?<=\\b|[^a-zA-Z])m[nuµmcdk]?|uoht|ni|\"|'|dy|dc|bW|" "T|t|zo|ts|twc|Wk?|aP[kMG]?|is[pk]|h|G|M|tfc|tfqs|tf|s"; static const std::string regexUnitlessFunctions = "soca|nisa|2nata|nata|hsoc|hnis|hnat|soc|nat|nis|pxe|gol|01gol"; static const std::string regexConstants = "e|ip|lomm|lom"; static const std::string regexNumber = "\\d+\\s*\\.?\\s*\\d*|\\.\\s*\\d+"; // If expression does not contain /*() or ^, this regex will not find anything if (copy.contains(QLatin1Char('/')) || copy.contains(QLatin1Char('*')) || copy.contains(QLatin1Char('(')) || copy.contains(QLatin1Char(')')) || copy.contains(QLatin1Char('^'))) { // Find units and replace 1/2mm -> 1/2*(1mm), 1^2mm -> 1^2*(1mm) QRegularExpression fixUnits( QString::fromStdString( "(" + regexUnits + ")(\\s*\\)|(?:\\*|(?:\\)(?:(?:\\s*(?:" + regexConstants + "|\\)(?:[^()]|(?R))*\\((?:" + regexUnitlessFunctions + ")|" + regexNumber + "))|(?R))*\\(|(?:\\s*(?:" + regexConstants + "|\\)(?:[^()]|(?R))*\\((?:" + regexUnitlessFunctions + ")|" + regexNumber + "))))+(?:[\\/\\^]|(.*$))(?!(" + regexUnits + ")))" ) ); QRegularExpressionMatch fixUnitsMatch = fixUnits.match(copyChunk); // 3rd capture group being filled indicates regex bailed out; no match. if (fixUnitsMatch.lastCapturedIndex() == 2 || (fixUnitsMatch.lastCapturedIndex() == 3 && fixUnitsMatch.captured(3).isEmpty())) { QString matchUnits = fixUnitsMatch.captured(1); QString matchNumbers = fixUnitsMatch.captured(2); copyChunk.replace( matchUnits + matchNumbers, QStringLiteral(")") + matchUnits + QStringLiteral("1(*") + matchNumbers ); } } // Add default units to string if none are present if (!copyChunk.contains(reverseUnitStr)) { // Fast check QRegularExpression unitsRe( QString::fromStdString( "(?<=\\b|[^a-zA-Z])(" + regexUnits + ")(?=\\b|[^a-zA-Z])|°|″|′|\"|'|\\p{L}\\.\\p{L}|\\[\\p{L}" ) ); QRegularExpressionMatch match = unitsRe.match(copyChunk); if (!match.hasMatch() && !copyChunk.isEmpty()) { // If no units are found, use // default units copyChunk.prepend( QStringLiteral(")") + reverseUnitStr + QStringLiteral("1(*") ); // Add units to the end of chunk *(1unit) } } std::reverse(copyChunk.begin(), copyChunk.end()); copy.replace( matchChunk.capturedStart() + lengthOffset, matchChunk.capturedEnd() - matchChunk.capturedStart(), copyChunk ); lengthOffset += copyChunk.length() - origionalChunk.length(); } ok = parseString(copy, res, value, path); // If result does not have unit: add default unit if (res.isDimensionless()) { res.setUnit(unit); } if (!ok) { // input may not be finished state = QValidator::Intermediate; } else if (value >= min && value <= max) { 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; } } if (state != QValidator::Acceptable) { res.setValue(max > 0 ? min : max); } return res; } QLocale locale; bool validInput; bool pendingEmit; bool normalize; bool checkRangeInExpression; QString validStr; Base::Quantity quantity; Base::Quantity cached; Base::Unit unit; double unitValue; QString unitStr; double maximum; double minimum; double singleStep; QuantitySpinBox* q_ptr; std::unique_ptr scheme; Q_DECLARE_PUBLIC(QuantitySpinBox) }; } // namespace Gui QuantitySpinBox::QuantitySpinBox(QWidget* parent) : QAbstractSpinBox(parent) , ExpressionSpinBox(this) , d_ptr(new QuantitySpinBoxPrivate(this)) { d_ptr->locale = locale(); this->setContextMenuPolicy(Qt::DefaultContextMenu); connect(lineEdit(), &QLineEdit::textChanged, this, &QuantitySpinBox::userInput); connect(this, &QuantitySpinBox::editingFinished, this, [&] { this->handlePendingEmit(true); }); } QuantitySpinBox::~QuantitySpinBox() = default; void QuantitySpinBox::bind(const App::ObjectIdentifier& _path) { ExpressionSpinBox::bind(_path); } void QuantitySpinBox::showIcon() { iconLabel->show(); } QString QuantitySpinBox::boundToName() const { if (isBound()) { std::string path = getPath().toString(); return QString::fromStdString(path); } return {}; } /** * @brief Create an object identifier by name. * * An identifier is written as document#documentobject.property.subproperty1...subpropertyN * document# may be dropped, in this case the active document is used. */ void QuantitySpinBox::setBoundToByName(const QString& name) { try { // get document App::Document* doc = App::GetApplication().getActiveDocument(); QStringList list = name.split(QLatin1Char('#')); if (list.size() > 1) { doc = App::GetApplication().getDocument(list.front().toLatin1()); list.pop_front(); } if (!doc) { qDebug() << "No such document"; return; } // first element is assumed to be the document name list = list.front().split(QLatin1Char('.')); // get object App::DocumentObject* obj = doc->getObject(list.front().toLatin1()); if (!obj) { qDebug() << "No object " << list.front() << " in document"; return; } list.pop_front(); // the rest of the list defines the property and eventually subproperties App::ObjectIdentifier path(obj); path.setDocumentName(std::string(doc->getName()), true); path.setDocumentObjectName(std::string(obj->getNameInDocument()), true); for (const auto& it : list) { path << App::ObjectIdentifier::Component::SimpleComponent(it.toLatin1().constData()); } if (path.getProperty()) { bind(path); } } catch (const Base::Exception& e) { qDebug() << e.what(); } } QString Gui::QuantitySpinBox::expressionText() const { try { if (hasExpression()) { return QString::fromStdString(getExpressionString()); } } catch (const Base::Exception& e) { qDebug() << e.what(); } return {}; } void QuantitySpinBox::evaluateExpression() { if (isBound() && getExpression()) { showValidExpression(Number::SetIfNumber); } } void Gui::QuantitySpinBox::setNumberExpression(App::NumberExpression* expr) { updateEdit(getUserString(expr->getQuantity())); handlePendingEmit(); } bool QuantitySpinBox::apply(const std::string& propName) { if (!ExpressionBinding::apply(propName)) { double dValue = value().getValue(); return assignToProperty(propName, dValue); } return false; } void QuantitySpinBox::resizeEvent(QResizeEvent* event) { QAbstractSpinBox::resizeEvent(event); resizeWidget(); } void Gui::QuantitySpinBox::keyPressEvent(QKeyEvent* event) { Q_D(QuantitySpinBox); const auto isEnter = event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return; if (d->normalize && isEnter && !isNormalized()) { // ensure that input is up to date handlePendingEmit(); normalize(); return; } if (!handleKeyEvent(event->text())) { QAbstractSpinBox::keyPressEvent(event); } if (isEnter) { returnPressed(); } } void Gui::QuantitySpinBox::paintEvent(QPaintEvent*) { QStyleOptionSpinBox opt; initStyleOption(&opt); drawControl(opt); } void QuantitySpinBox::updateText(const Quantity& quant) { Q_D(QuantitySpinBox); double dFactor; QString txt = getUserString(quant, dFactor, d->unitStr); d->unitValue = quant.getValue() / dFactor; updateEdit(txt); handlePendingEmit(); } void QuantitySpinBox::updateEdit(const QString& text) { Q_D(QuantitySpinBox); QLineEdit* edit = lineEdit(); bool empty = edit->text().isEmpty(); int cursor = edit->cursorPosition(); int selsize = edit->selectedText().size(); edit->setText(text); cursor = qBound(0, cursor, qMax(0, edit->displayText().size() - d->unitStr.size())); if (selsize > 0) { edit->setSelection(0, cursor); } else { edit->setCursorPosition(empty ? 0 : cursor); } } void QuantitySpinBox::validateInput() { Q_D(QuantitySpinBox); QValidator::State state; QString text = lineEdit()->text(); const App::ObjectIdentifier& path = getPath(); d->validateAndInterpret(text, state, path); if (state != QValidator::Acceptable) { updateEdit(d->validStr); } handlePendingEmit(); } Base::Quantity QuantitySpinBox::value() const { Q_D(const QuantitySpinBox); return d->quantity; } double QuantitySpinBox::rawValue() const { Q_D(const QuantitySpinBox); return d->quantity.getValue(); } void QuantitySpinBox::normalize() { // this does not really change the value, only the representation QSignalBlocker blocker(this); Q_D(const QuantitySpinBox); return setValue(d->quantity); } bool QuantitySpinBox::isNormalized() { static const QRegularExpression operators( QStringLiteral("[+\\-/*]"), QRegularExpression::CaseInsensitiveOption ); Q_D(const QuantitySpinBox); // this check is two level // 1. We consider every string that does not contain operators as normalized // 2. If it does contain operators we check if it differs from normalized input - as some // operators like - can be allowed even in normalized case. return !d->validStr.contains(operators) || d->validStr.toStdString() == d->quantity.getUserString(); } void QuantitySpinBox::setValue(const Base::Quantity& value) { Q_D(QuantitySpinBox); d->quantity = value; // check limits if (d->quantity.getValue() > d->maximum) { d->quantity.setValue(d->maximum); } if (d->quantity.getValue() < d->minimum) { d->quantity.setValue(d->minimum); } d->unit = value.getUnit(); updateText(value); } void QuantitySpinBox::setValue(double value) { Q_D(QuantitySpinBox); Base::QuantityFormat currentformat = d->quantity.getFormat(); auto quantity = Base::Quantity(value, d->unit); quantity.setFormat(currentformat); setValue(quantity); } bool QuantitySpinBox::autoNormalize() const { Q_D(const QuantitySpinBox); return d->normalize; } void QuantitySpinBox::setAutoNormalize(bool normalize) { Q_D(QuantitySpinBox); d->normalize = normalize; } bool QuantitySpinBox::hasValidInput() const { Q_D(const QuantitySpinBox); return d->validInput; } // Gets called after call of 'validateAndInterpret' void QuantitySpinBox::userInput(const QString& text) { Q_D(QuantitySpinBox); d->pendingEmit = true; QString tmp = text; Base::Quantity res; const App::ObjectIdentifier& path = getPath(); if (d->validate(tmp, res, path)) { d->validStr = tmp; d->validInput = true; } else { d->validInput = false; // only emit signal to reset EditableDatumLabel if the input is truly empty or has // no meaningful number don't emit for partially typed numbers like "71." which are // temporarily invalid const QString trimmedText = text.trimmed(); static const QRegularExpression partialNumberRegex(QStringLiteral(R"([+-]?(\d+)?(\.,\d*)?)")); if (trimmedText.isEmpty() || !trimmedText.contains(partialNumberRegex)) { // we have to emit here signal explicitly as validator will not pass // this value further but we want to check it to disable isSet flag if // it has been set previously Q_EMIT valueChanged(d->quantity.getValue()); } return; } if (keyboardTracking()) { d->cached = res; handlePendingEmit(false); } else { d->cached = res; } } void QuantitySpinBox::openFormulaDialog() { Q_ASSERT(isBound()); Q_D(const QuantitySpinBox); auto box = new Gui::Dialog::DlgExpressionInput(getPath(), getExpression(), d->unit, this); if (d->checkRangeInExpression) { box->setRange(d->minimum, d->maximum); } QObject::connect(box, &Gui::Dialog::DlgExpressionInput::finished, [this, box]() { if (box->result() == QDialog::Accepted) { setExpression(box->getExpression()); } else if (box->discardedFormula()) { setExpression(std::shared_ptr()); } updateExpression(); box->deleteLater(); Q_EMIT showFormulaDialog(false); }); box->show(); QPoint pos = mapToGlobal(QPoint(0, 0)); box->move(pos - box->expressionPosition()); Gui::adjustDialogPosition(box); Q_EMIT showFormulaDialog(true); } void QuantitySpinBox::handlePendingEmit(bool updateUnit /* = true */) { updateFromCache(true, updateUnit); } void QuantitySpinBox::updateFromCache(bool notify, bool updateUnit /* = true */) { Q_D(QuantitySpinBox); if (d->pendingEmit) { double factor; const Base::Quantity& res = d->cached; auto tmpUnit(d->unitStr); QString text = getUserString(res, factor, updateUnit ? d->unitStr : tmpUnit); d->unitValue = res.getValue() / factor; d->quantity = res; // signaling if (notify) { d->pendingEmit = false; Q_EMIT valueChanged(res); Q_EMIT valueChanged(res.getValue()); Q_EMIT textChanged(text); } } } Base::Unit QuantitySpinBox::unit() const { Q_D(const QuantitySpinBox); return d->unit; } void QuantitySpinBox::setUnit(const Base::Unit& unit) { Q_D(QuantitySpinBox); d->unit = unit; d->quantity.setUnit(unit); updateText(d->quantity); } void QuantitySpinBox::setUnitText(const QString& str) { try { Base::Quantity quant = Base::Quantity::parse(str.toStdString()); setUnit(quant.getUnit()); } catch (const Base::ParserError&) { } } QString QuantitySpinBox::unitText() { Q_D(QuantitySpinBox); return d->unitStr; } double QuantitySpinBox::singleStep() const { Q_D(const QuantitySpinBox); return d->singleStep; } void QuantitySpinBox::setSingleStep(double value) { Q_D(QuantitySpinBox); if (value >= 0) { d->singleStep = value; } } double QuantitySpinBox::minimum() const { Q_D(const QuantitySpinBox); return d->minimum; } void QuantitySpinBox::setMinimum(double minimum) { Q_D(QuantitySpinBox); d->minimum = minimum; } double QuantitySpinBox::maximum() const { Q_D(const QuantitySpinBox); return d->maximum; } void QuantitySpinBox::setMaximum(double maximum) { Q_D(QuantitySpinBox); d->maximum = maximum; } void QuantitySpinBox::setRange(double minimum, double maximum) { Q_D(QuantitySpinBox); d->minimum = minimum; d->maximum = maximum; } void QuantitySpinBox::checkRangeInExpression(bool on) { Q_D(QuantitySpinBox); d->checkRangeInExpression = on; } bool QuantitySpinBox::isCheckedRangeInExpresion() const { Q_D(const QuantitySpinBox); return d->checkRangeInExpression; } int QuantitySpinBox::decimals() const { Q_D(const QuantitySpinBox); return d->quantity.getFormat().getPrecision(); } void QuantitySpinBox::setDecimals(int v) { Q_D(QuantitySpinBox); Base::QuantityFormat f = d->quantity.getFormat(); f.setPrecision(v); d->quantity.setFormat(f); updateText(d->quantity); } void QuantitySpinBox::setSchema(const int s) { Q_D(QuantitySpinBox); d->scheme = Base::UnitsApi::createSchema(s); updateText(d->quantity); } void QuantitySpinBox::clearSchema() { Q_D(QuantitySpinBox); d->scheme = nullptr; updateText(d->quantity); } QString QuantitySpinBox::getUserString(const Base::Quantity& val, double& factor, QString& unitString) const { Q_D(const QuantitySpinBox); std::string unitStr; const std::string str = d->scheme ? val.getUserString(d->scheme.get(), factor, unitStr) : val.getUserString(factor, unitStr); unitString = QString::fromStdString(unitStr); return QString::fromStdString(str); } QString QuantitySpinBox::getUserString(const Base::Quantity& val) const { Q_D(const QuantitySpinBox); std::string str; if (d->scheme) { double factor; std::string unitString; str = val.getUserString(d->scheme.get(), factor, unitString); } else { str = val.getUserString(); } return QString::fromStdString(str); } void QuantitySpinBox::setExpression(std::shared_ptr expr) { ExpressionSpinBox::setExpression(expr); } QAbstractSpinBox::StepEnabled QuantitySpinBox::stepEnabled() const { Q_D(const QuantitySpinBox); if (isReadOnly() /* || !d->validInput*/) { return StepNone; } if (wrapping()) { return StepEnabled(StepUpEnabled | StepDownEnabled); } StepEnabled ret = StepNone; if (d->quantity.getValue() < d->maximum) { ret |= StepUpEnabled; } if (d->quantity.getValue() > d->minimum) { ret |= StepDownEnabled; } return ret; } void QuantitySpinBox::stepBy(int steps) { Q_D(QuantitySpinBox); updateFromCache(false); double step = d->singleStep * steps; double val = d->unitValue + step; if (val > d->maximum) { val = d->maximum; } else if (val < d->minimum) { val = d->minimum; } Quantity quant(val, d->unitStr.toStdString()); updateText(quant); updateFromCache(true); update(); selectNumber(); } QSize QuantitySpinBox::sizeForText(const QString& txt) const { const QFontMetrics fm(fontMetrics()); int h = lineEdit()->sizeHint().height(); int w = QtTools::horizontalAdvance(fm, txt); w += 2; // cursor blinking space w += iconHeight; QStyleOptionSpinBox opt; initStyleOption(&opt); QSize hint(w, h); QSize size = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this); return size; } QSize QuantitySpinBox::sizeHint() const { return sizeHintCalculator(lineEdit()->sizeHint().height()); } QSize QuantitySpinBox::minimumSizeHint() const { return sizeHintCalculator(lineEdit()->minimumSizeHint().height()); } QSize QuantitySpinBox::sizeHintCalculator(int h) const { Q_D(const QuantitySpinBox); ensurePolished(); const QFontMetrics fm(fontMetrics()); int w = 0; constexpr int maxStrLen = 9; QString s; QString fixedContent = QLatin1String(" "); Base::Quantity q(d->quantity); q.setValue(d->maximum); s = textFromValue(q); s.truncate(maxStrLen); s += fixedContent; w = qMax(w, QtTools::horizontalAdvance(fm, s)); w += 2; // cursor blinking space w += iconHeight; QStyleOptionSpinBox opt; initStyleOption(&opt); QSize hint(w, h); QSize size = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this); return size; } void QuantitySpinBox::showEvent(QShowEvent* event) { Q_D(QuantitySpinBox); QAbstractSpinBox::showEvent(event); bool selected = lineEdit()->hasSelectedText(); updateText(d->quantity); if (selected) { selectNumber(); } } void QuantitySpinBox::hideEvent(QHideEvent* event) { handlePendingEmit(); QAbstractSpinBox::hideEvent(event); } void QuantitySpinBox::closeEvent(QCloseEvent* event) { handlePendingEmit(); QAbstractSpinBox::closeEvent(event); } bool QuantitySpinBox::event(QEvent* event) { return QAbstractSpinBox::event(event); } void QuantitySpinBox::focusInEvent(QFocusEvent* event) { bool hasSel = lineEdit()->hasSelectedText(); QAbstractSpinBox::focusInEvent(event); if (event->reason() == Qt::TabFocusReason || event->reason() == Qt::BacktabFocusReason || event->reason() == Qt::ShortcutFocusReason) { if (isBound() && getExpression() && lineEdit()->isReadOnly()) { auto helpEvent = new QHelpEvent( QEvent::ToolTip, QPoint(0, rect().height()), mapToGlobal(QPoint(0, rect().height())) ); QApplication::postEvent(this, helpEvent); lineEdit()->setSelection(0, 0); } else { if (!hasSel) { selectNumber(); } } } } void QuantitySpinBox::focusOutEvent(QFocusEvent* event) { Q_D(const QuantitySpinBox); validateInput(); if (d->normalize) { normalize(); } QToolTip::hideText(); QAbstractSpinBox::focusOutEvent(event); } void QuantitySpinBox::clear() { QAbstractSpinBox::clear(); } void QuantitySpinBox::selectNumber() { QString expr = QStringLiteral("^([%1%2]?[0-9\\%3]*)\\%4?([0-9]+(%5[%1%2]?[0-9]+)?)") .arg(locale().negativeSign()) .arg(locale().positiveSign()) .arg(locale().groupSeparator()) .arg(locale().decimalPoint()) .arg(locale().exponential()); auto rmatch = QRegularExpression(expr).match(lineEdit()->text()); if (rmatch.hasMatch()) { lineEdit()->setSelection(0, rmatch.capturedLength()); } } QString QuantitySpinBox::textFromValue(const Base::Quantity& value) const { QString str = getUserString(value); if (qAbs(value.getValue()) >= 1000.0) { str.remove(locale().groupSeparator()); } return str; } Base::Quantity QuantitySpinBox::valueFromText(const QString& text) const { Q_D(const QuantitySpinBox); QString copy = text; QValidator::State state = QValidator::Acceptable; const App::ObjectIdentifier& path = getPath(); Base::Quantity quant = d->validateAndInterpret(copy, state, path); if (state != QValidator::Acceptable) { fixup(copy); quant = d->validateAndInterpret(copy, state, path); } return quant; } QValidator::State QuantitySpinBox::validate(QString& text, int& pos) const { Q_D(const QuantitySpinBox); Q_UNUSED(pos) QValidator::State state; const App::ObjectIdentifier& path = getPath(); d->validateAndInterpret(text, state, path); return state; } void QuantitySpinBox::fixup(QString& input) const { input.remove(locale().groupSeparator()); } #include "moc_QuantitySpinBox.cpp" #include "moc_QuantitySpinBox_p.cpp"