diff --git a/libs/ui/tests/kis_parsespinboxestest.cpp b/libs/ui/tests/kis_parsespinboxestest.cpp index ebfe11f49e..da1c0980d5 100644 --- a/libs/ui/tests/kis_parsespinboxestest.cpp +++ b/libs/ui/tests/kis_parsespinboxestest.cpp @@ -1,285 +1,365 @@ /* * Copyright (c) 2016 Laurent Valentin Jospin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_parsespinboxestest.h" #include "kis_numparser.h" #include "kis_doubleparsespinbox.h" #include "kis_intparsespinbox.h" #include #include const QStringList KisParseSpinBoxesTest::doubleExprs = {"1", "-12", "7.9 - 12", "cos(90)*2", "cos(acos(-1)+1*3^2.0)*2 + sin(3)/2"}; + +const QStringList KisParseSpinBoxesTest::doubleWrongExprs = {"abc", + "1/", + "7.9 + 12*", + "cos(90)*2 + ", + "23.0/0", + "0.0/0.0"}; + const QStringList KisParseSpinBoxesTest::intExprs = {"12", "-12", "-12.3", "12.7 - 25", "12.7", "12*1.5", "12/2.5", "518*2/3"}; +const QStringList KisParseSpinBoxesTest::intWrongExprs = {"abc", + "12.5/2 +", + "12*", + "12/0"}; + KisParseSpinBoxesTest::KisParseSpinBoxesTest() : QObject() { } void KisParseSpinBoxesTest::testDoubleParseNormal() { KisDoubleParseSpinBox spinBox; spinBox.setDecimals(3); spinBox.setMaximum(9999.0); spinBox.setMinimum(-9999.0); for (int i = 0; i < doubleExprs.size(); i++) { spinBox.clearFocus(); spinBox.clear(); //clear all QTest::keyClicks(&spinBox, doubleExprs[i]); spinBox.clearFocus(); double resultParser = KisNumericParser::parseSimpleMathExpr(doubleExprs[i]); double valueSpinBox = spinBox.value(); bool test = resultParser == valueSpinBox || qAbs(resultParser - valueSpinBox) < 1e-2; QVERIFY2(test, QString("Failed with expression %1, result is %2, value is %3") .arg(doubleExprs[i]).arg(resultParser).arg(valueSpinBox).toStdString().c_str()); spinBox.setValue(0); } } void KisParseSpinBoxesTest::testDoubleParseProblem() { - QStringList exprs = {"abc", - "1/", - "7.9 + 12*", - "cos(90)*2 + ", - "23.0/0", - "0.0/0.0"}; - //error can happen with incomplete or incorrect expressions, inf or nan values. KisDoubleParseSpinBox spinBox; spinBox.setMaximum(9999.0); spinBox.setMinimum(-9999.0); - for (int i = 0; i < exprs.size(); i++) { + for (int i = 0; i < doubleWrongExprs.size(); i++) { spinBox.clearFocus(); spinBox.clear(); //clear all - QTest::keyClicks(&spinBox, exprs[i]); + QTest::keyClicks(&spinBox, doubleWrongExprs[i]); spinBox.clearFocus(); QVERIFY2(!spinBox.isLastValid(), QString("SpinBox is in a valid state with expression %1, but shouldn't.") - .arg(exprs[i]).toStdString().c_str()); + .arg(doubleWrongExprs[i]).toStdString().c_str()); spinBox.setValue(0.0); QVERIFY2(spinBox.isLastValid(), QString("SpinBox unsable to recover error free state after a value reset.") .toStdString().c_str()); spinBox.setValue(0); } } void KisParseSpinBoxesTest::testDoubleParseWithSuffix(){ + QString suff = "px"; + KisDoubleParseSpinBox spinBox; spinBox.setDecimals(3); spinBox.setMaximum(9999.0); spinBox.setMinimum(-9999.0); - spinBox.setSuffix("px"); + spinBox.setSuffix(suff); for (int i = 0; i < doubleExprs.size(); i++) { spinBox.clearFocus(); spinBox.clear(); //clear all QTest::keyClicks(&spinBox, doubleExprs[i]); spinBox.clearFocus(); double resultParser = KisNumericParser::parseSimpleMathExpr(doubleExprs[i]); double valueSpinBox = spinBox.value(); bool test = resultParser == valueSpinBox || qAbs(resultParser - valueSpinBox) < 1e-2; QVERIFY2(test, QString("Failed with expression %1, result is %2, value is %3") .arg(doubleExprs[i]).arg(resultParser).arg(valueSpinBox).toStdString().c_str()); spinBox.setValue(0); } + + //verify that the suffix don't appear in the clean text in case of error. + for (int i = 0; i < doubleWrongExprs.size(); i++) { + + if (doubleWrongExprs[i].endsWith(suff)) { + continue; + } + + spinBox.clearFocus(); + spinBox.clear(); //clear all + QTest::keyClicks(&spinBox, doubleWrongExprs[i]); + spinBox.clearFocus(); + + QVERIFY2(!spinBox.cleanText().endsWith(suff), "SpinBox failed to remove suffix from clean text in error state."); + + spinBox.setValue(0.0); + + } } void KisParseSpinBoxesTest::testDoubleParseWithPrefix() { + QString preff = "px"; + KisDoubleParseSpinBox spinBox; spinBox.setDecimals(3); spinBox.setMaximum(9999.0); spinBox.setMinimum(-9999.0); - spinBox.setPrefix("px"); + spinBox.setPrefix(preff); for (int i = 0; i < doubleExprs.size(); i++) { spinBox.clearFocus(); spinBox.clear(); //clear all QTest::keyClicks(&spinBox, doubleExprs[i]); spinBox.clearFocus(); double resultParser = KisNumericParser::parseSimpleMathExpr(doubleExprs[i]); double valueSpinBox = spinBox.value(); bool test = resultParser == valueSpinBox || qAbs(resultParser - valueSpinBox) < 1e-2; QVERIFY2(test, QString("Failed with expression %1, result is %2, value is %3") .arg(doubleExprs[i]).arg(resultParser).arg(valueSpinBox).toStdString().c_str()); spinBox.setValue(0); } + + //verify that the prefix don't appear in the clean text in case of error. + for (int i = 0; i < doubleWrongExprs.size(); i++) { + + if (doubleWrongExprs[i].endsWith(preff)) { + continue; + } + + spinBox.clearFocus(); + spinBox.clear(); //clear all + QTest::keyClicks(&spinBox, doubleWrongExprs[i]); + spinBox.clearFocus(); + + QVERIFY2(!spinBox.cleanText().startsWith(preff), "SpinBox failed to remove prefix from clean text in error state."); + + spinBox.setValue(0.0); + + } } void KisParseSpinBoxesTest::testIntParseNormal() { KisIntParseSpinBox spinBox; spinBox.setMaximum(999); spinBox.setMinimum(-999); for (int i = 0; i < intExprs.size(); i++) { spinBox.clearFocus(); spinBox.clear(); //clear all QTest::keyClicks(&spinBox, intExprs[i]); spinBox.clearFocus(); int resultParser = KisNumericParser::parseIntegerMathExpr(intExprs[i]); int valueSpinBox = spinBox.value(); bool test = resultParser == valueSpinBox; QVERIFY2(test, QString("Failed with expression %1, result is %2, value is %3") .arg(intExprs[i]).arg(resultParser).arg(valueSpinBox).toStdString().c_str()); spinBox.setValue(0); } } void KisParseSpinBoxesTest::testIntParseProblem() { - QStringList exprs = {"abc", - "12.5/2 +", - "12*", - "12/0"}; //errors can happen with incorrect or incomplete expressions, or division by 0. KisIntParseSpinBox spinBox; spinBox.setMaximum(999); spinBox.setMinimum(-999); - for (int i = 0; i < exprs.size(); i++) { + for (int i = 0; i < intWrongExprs.size(); i++) { spinBox.clearFocus(); spinBox.clear(); //clear all - QTest::keyClicks(&spinBox, exprs[i]); + QTest::keyClicks(&spinBox, intWrongExprs[i]); spinBox.clearFocus(); QVERIFY2(!spinBox.isLastValid(), QString("SpinBox is in a valid state with expression %1, but shouldn't.") - .arg(exprs[i]).toStdString().c_str()); + .arg(intWrongExprs[i]).toStdString().c_str()); spinBox.setValue(0); QVERIFY2(spinBox.isLastValid(), QString("SpinBox unsable to recover error free state after a value reset.") .toStdString().c_str()); spinBox.setValue(0); } } void KisParseSpinBoxesTest::testIntParseWithSuffix() { + QString suff = "px"; KisIntParseSpinBox spinBox; spinBox.setMaximum(999); spinBox.setMinimum(-999); - spinBox.setSuffix("px"); + spinBox.setSuffix(suff); for (int i = 0; i < intExprs.size(); i++) { spinBox.clearFocus(); spinBox.clear(); //clear all QTest::keyClicks(&spinBox, intExprs[i]); spinBox.clearFocus(); int resultParser = KisNumericParser::parseIntegerMathExpr(intExprs[i]); int valueSpinBox = spinBox.value(); bool test = resultParser == valueSpinBox; QVERIFY2(test, QString("Failed with expression %1, result is %2, value is %3") .arg(intExprs[i]).arg(resultParser).arg(valueSpinBox).toStdString().c_str()); spinBox.setValue(0); } + //verify that the suffix don't appear in the clean text in case of error. + for (int i = 0; i < intWrongExprs.size(); i++) { + + if (intWrongExprs[i].endsWith(suff)) { + continue; + } + + spinBox.clearFocus(); + spinBox.clear(); //clear all + QTest::keyClicks(&spinBox, intWrongExprs[i]); + spinBox.clearFocus(); + + QVERIFY2(!spinBox.cleanText().endsWith(suff), "SpinBox failed to remove suffix from clean text in error state."); + + spinBox.setValue(0.0); + + } + } void KisParseSpinBoxesTest::testIntParseWithPrefix() { + QString preff = "px"; KisIntParseSpinBox spinBox; spinBox.setMaximum(999); spinBox.setMinimum(-999); - spinBox.setPrefix("px"); + spinBox.setPrefix(preff); for (int i = 0; i < intExprs.size(); i++) { spinBox.clearFocus(); spinBox.clear(); //clear all QTest::keyClicks(&spinBox, intExprs[i]); spinBox.clearFocus(); int resultParser = KisNumericParser::parseIntegerMathExpr(intExprs[i]); int valueSpinBox = spinBox.value(); bool test = resultParser == valueSpinBox; QVERIFY2(test, QString("Failed with expression %1, result is %2, value is %3") .arg(intExprs[i]).arg(resultParser).arg(valueSpinBox).toStdString().c_str()); spinBox.setValue(0); } + //verify that the prefix don't appear in the clean text in case of error. + for (int i = 0; i < intWrongExprs.size(); i++) { + + if (intWrongExprs[i].startsWith(preff)) { + continue; + } + + spinBox.clearFocus(); + spinBox.clear(); //clear all + QTest::keyClicks(&spinBox, intWrongExprs[i]); + spinBox.clearFocus(); + + QVERIFY2(!spinBox.cleanText().startsWith(preff), "SpinBox failed to remove prefix from clean text in error state."); + + spinBox.setValue(0.0); + + } + } QTEST_MAIN(KisParseSpinBoxesTest) diff --git a/libs/ui/tests/kis_parsespinboxestest.h b/libs/ui/tests/kis_parsespinboxestest.h index 2f3e4e3d7e..daf8d0ddf5 100644 --- a/libs/ui/tests/kis_parsespinboxestest.h +++ b/libs/ui/tests/kis_parsespinboxestest.h @@ -1,49 +1,51 @@ /* * Copyright (c) 2016 Laurent Valentin Jospin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISPARSESPINBOXESTEST_H #define KISPARSESPINBOXESTEST_H #include #include class KisParseSpinBoxesTest : public QObject { Q_OBJECT public: explicit KisParseSpinBoxesTest(); private Q_SLOTS: void testDoubleParseNormal(); void testDoubleParseProblem(); void testDoubleParseWithSuffix(); void testDoubleParseWithPrefix(); void testIntParseNormal(); void testIntParseProblem(); void testIntParseWithSuffix(); void testIntParseWithPrefix(); private: const static QStringList doubleExprs; + const static QStringList doubleWrongExprs; const static QStringList intExprs; + const static QStringList intWrongExprs; }; #endif // KISPARSESPINBOXESTEST_H diff --git a/libs/ui/widgets/kis_doubleparsespinbox.cpp b/libs/ui/widgets/kis_doubleparsespinbox.cpp index 6fc522bdef..d225978c87 100644 --- a/libs/ui/widgets/kis_doubleparsespinbox.cpp +++ b/libs/ui/widgets/kis_doubleparsespinbox.cpp @@ -1,181 +1,187 @@ /* * Copyright (c) 2016 Laurent Valentin Jospin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_doubleparsespinbox.h" #include "kis_numparser.h" #include #include #include #include KisDoubleParseSpinBox::KisDoubleParseSpinBox(QWidget *parent) : QDoubleSpinBox(parent), _isLastValid(true) { _lastExprParsed = new QString("0.0"); connect(this, SIGNAL(noMoreParsingError()), this, SLOT(clearErrorStyle())); //hack to let the clearError be called, even if the value changed method is the one from QDoubleSpinBox. connect(this, SIGNAL(valueChanged(double)), this, SLOT(clearError())); connect(this, SIGNAL(errorWhileParsing(QString)), this, SLOT(setErrorStyle())); _oldValue = value(); _warningIcon = new QLabel(this); if (QFile(":/./16_light_warning.svg").exists()) { _warningIcon->setPixmap(QIcon(":/./16_light_warning.svg").pixmap(16, 16)); } else { _warningIcon->setText("!"); } _warningIcon->setStyleSheet("background:transparent;"); _warningIcon->move(1, 1); _warningIcon->setVisible(false); } KisDoubleParseSpinBox::~KisDoubleParseSpinBox() { //needed to avoid a segfault during destruction. delete _lastExprParsed; } double KisDoubleParseSpinBox::valueFromText(const QString & text) const { *_lastExprParsed = text; bool ok; double ret; if ( (suffix().isEmpty() || !text.endsWith(suffix())) && (prefix().isEmpty() || !text.startsWith(prefix())) ) { + ret = KisNumericParser::parseSimpleMathExpr(text, &ok); + } else { + QString expr = text; if (text.endsWith(suffix())) { expr.remove(text.size()-suffix().size(), suffix().size()); } if(text.startsWith(prefix())){ expr.remove(0, prefix().size()); } + *_lastExprParsed = expr; + ret = KisNumericParser::parseSimpleMathExpr(expr, &ok); + } if(isnan(ret) || isinf(ret)){ ok = false; } if (!ok) { if (_isLastValid) { _oldValue = value(); } _isLastValid = false; ret = _oldValue; //in case of error set to minimum. } else { if (!_isLastValid) { _oldValue = ret; } _isLastValid = true; } return ret; } QString KisDoubleParseSpinBox::textFromValue(double val) const { if (!_isLastValid) { emit errorWhileParsing(*_lastExprParsed); return *_lastExprParsed; } emit noMoreParsingError(); return QDoubleSpinBox::textFromValue(val); } QValidator::State KisDoubleParseSpinBox::validate ( QString & input, int & pos ) const { Q_UNUSED(input); Q_UNUSED(pos); return QValidator::Acceptable; } void KisDoubleParseSpinBox::stepBy(int steps) { _isLastValid = true; //reset to valid state so we can use the up and down buttons. emit noMoreParsingError(); QDoubleSpinBox::stepBy(steps); } void KisDoubleParseSpinBox::setValue(double value) { if (!hasFocus()) { clearError(); } QDoubleSpinBox::setValue(value); } void KisDoubleParseSpinBox::setErrorStyle() { if (!_isLastValid) { setStyleSheet("Background: red; color: white; padding-left: 18px;"); _warningIcon->move(-14, size().height()/2 - 16/2); _warningIcon->setVisible(true); } } void KisDoubleParseSpinBox::clearErrorStyle() { if (_isLastValid) { _warningIcon->setVisible(false); setStyleSheet(""); } } void KisDoubleParseSpinBox::clearError() { _isLastValid = true; emit noMoreParsingError(); _oldValue = value(); clearErrorStyle(); } diff --git a/libs/ui/widgets/kis_intparsespinbox.cpp b/libs/ui/widgets/kis_intparsespinbox.cpp index 28a72514d5..8d8f349f69 100644 --- a/libs/ui/widgets/kis_intparsespinbox.cpp +++ b/libs/ui/widgets/kis_intparsespinbox.cpp @@ -1,190 +1,196 @@ /* * Copyright (c) 2016 Laurent Valentin Jospin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_intparsespinbox.h" #include "kis_numparser.h" #include #include #include #include KisIntParseSpinBox::KisIntParseSpinBox(QWidget *parent) : QSpinBox(parent), _isLastValid(true) { _lastExprParsed = new QString("0"); connect(this, SIGNAL(noMoreParsingError()), this, SLOT(clearErrorStyle())); //hack to let the clearError be called, even if the value changed method is the one from QSpinBox. connect(this, SIGNAL(valueChanged(int)), this, SLOT(clearError())); connect(this, SIGNAL(errorWhileParsing(QString)), this, SLOT(setErrorStyle())); _oldVal = value(); _warningIcon = new QLabel(this); if (QFile(":/./16_light_warning.svg").exists()) { _warningIcon->setPixmap(QIcon(":/./16_light_warning.svg").pixmap(16, 16)); } else { _warningIcon->setText("!"); } _warningIcon->setStyleSheet("background:transparent;"); _warningIcon->move(1, 1); _warningIcon->setVisible(false); } KisIntParseSpinBox::~KisIntParseSpinBox() { //needed to avoid a segfault during destruction. delete _lastExprParsed; } int KisIntParseSpinBox::valueFromText(const QString & text) const { *_lastExprParsed = text; bool ok; int val; if ( (suffix().isEmpty() || !text.endsWith(suffix())) && (prefix().isEmpty() || !text.startsWith(prefix())) ) { + val = KisNumericParser::parseIntegerMathExpr(text, &ok); + } else { + QString expr = text; if (text.endsWith(suffix())) { expr.remove(text.size()-suffix().size(), suffix().size()); } if(text.startsWith(prefix())){ expr.remove(0, prefix().size()); } + *_lastExprParsed = expr; + val = KisNumericParser::parseIntegerMathExpr(expr, &ok); + } if (text.trimmed().isEmpty()) { //an empty text is considered valid in this case. ok = true; } if (!ok) { if (_isLastValid == true) { _oldVal = value(); } _isLastValid = false; //emit errorWhileParsing(text); //if uncommented become red everytime the string is wrong. val = _oldVal; } else { if (_isLastValid == false) { _oldVal = val; } _isLastValid = true; //emit noMoreParsingError(); } return val; } QString KisIntParseSpinBox::textFromValue(int val) const { if (!_isLastValid) { emit errorWhileParsing(*_lastExprParsed); return *_lastExprParsed; } emit noMoreParsingError(); return QSpinBox::textFromValue(val); } QValidator::State KisIntParseSpinBox::validate ( QString & input, int & pos ) const { Q_UNUSED(input); Q_UNUSED(pos); //this simple definition is sufficient for the moment //TODO: see if needed to get something more complex. return QValidator::Acceptable; } void KisIntParseSpinBox::stepBy(int steps) { _isLastValid = true; emit noMoreParsingError(); QSpinBox::stepBy(steps); } void KisIntParseSpinBox::setValue(int val) { if (!hasFocus()) { clearError(); } QSpinBox::setValue(val); } void KisIntParseSpinBox::setErrorStyle() { if (!_isLastValid) { setStyleSheet("Background: red; color: white; padding-left: 18px;"); _warningIcon->move(-14, size().height()/2 - 16/2); _warningIcon->setVisible(true); } } void KisIntParseSpinBox::clearErrorStyle() { if (_isLastValid) { _warningIcon->setVisible(false); setStyleSheet(""); } } void KisIntParseSpinBox::clearError() { _isLastValid = true; emit noMoreParsingError(); _oldVal = value(); clearErrorStyle(); }