diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt --- a/libs/ui/CMakeLists.txt +++ b/libs/ui/CMakeLists.txt @@ -247,6 +247,8 @@ widgets/kis_lod_availability_widget.cpp widgets/kis_color_label_selector_widget.cpp widgets/kis_color_filter_combo.cpp + widgets/kis_doubleparsespinbox.cpp + widgets/kis_intparsespinbox.cpp input/kis_input_manager.cpp input/kis_input_manager_p.cpp input/kis_extended_modifiers_mapper.cpp @@ -356,6 +358,8 @@ KisPaletteModel.cpp KisColorsetChooser.cpp KisSaveGroupVisitor.cpp + + utils/kis_numparser.cpp ) if(WIN32) @@ -518,6 +522,7 @@ $ $ $ + $ $ $ ) diff --git a/libs/ui/tests/CMakeLists.txt b/libs/ui/tests/CMakeLists.txt --- a/libs/ui/tests/CMakeLists.txt +++ b/libs/ui/tests/CMakeLists.txt @@ -259,3 +259,15 @@ set(kis_stabilized_events_sampler_test_SRCS kis_stabilized_events_sampler_test.cpp) kde4_add_unit_test(KisStabilizedEventsSamplerTest TESTNAME krita-ui-StabilizedEventsSamplerTest ${kis_stabilized_events_sampler_test_SRCS}) target_link_libraries(KisStabilizedEventsSamplerTest kritaui Qt5::Test) + +########### next target ############### + +set(kis_simplemathparsertest_SRCS kis_simplemathparsertest.cpp) +kde4_add_unit_test(KisSimpleMathParserTest TESTNAME krita-ui-KisSimpleMathParserTest ${kis_simplemathparsertest_SRCS}) +target_link_libraries(KisSimpleMathParserTest kritaui Qt5::Test) + +########### next target ############### + +set(kis_parsespinboxestest_SRCS kis_parsespinboxestest.cpp) +kde4_add_unit_test(KisParseSpinBoxesTest TESTNAME krita-ui-KisParseSpinBoxesTest ${kis_parsespinboxestest_SRCS}) +target_link_libraries(KisParseSpinBoxesTest kritaui Qt5::Test) diff --git a/libs/ui/tests/kis_parsespinboxestest.h b/libs/ui/tests/kis_parsespinboxestest.h new file mode 100644 --- /dev/null +++ b/libs/ui/tests/kis_parsespinboxestest.h @@ -0,0 +1,21 @@ +#ifndef KISPARSESPINBOXESTEST_H +#define KISPARSESPINBOXESTEST_H + +#include + +class KisParseSpinBoxesTest : public QObject +{ + Q_OBJECT + +public: + explicit KisParseSpinBoxesTest(); + +private Q_SLOTS: + + void testDoubleParseNormal(); + void testDoubleParseProblem(); + void testIntParseNormal(); + void testIntParseProblem(); +}; + +#endif // KISPARSESPINBOXESTEST_H diff --git a/libs/ui/tests/kis_parsespinboxestest.cpp b/libs/ui/tests/kis_parsespinboxestest.cpp new file mode 100644 --- /dev/null +++ b/libs/ui/tests/kis_parsespinboxestest.cpp @@ -0,0 +1,157 @@ +#include "kis_parsespinboxestest.h" + +#include "kis_numparser.h" +#include "kis_doubleparsespinbox.h" +#include "kis_intparsespinbox.h" + +#include +#include + +KisParseSpinBoxesTest::KisParseSpinBoxesTest() : QObject() +{ + +} + +void KisParseSpinBoxesTest::testDoubleParseNormal() +{ + + QStringList exprs = {"1", + "-12", + "7.9 - 12", + "cos(90)*2", + "cos(acos(-1)+1*3^2.0)*2 + sin(3)/2"}; + + KisDoubleParseSpinBox spinBox; + spinBox.setDecimals(3); + spinBox.setMaximum(9999.0); + spinBox.setMinimum(-9999.0); + + for (int i = 0; i < exprs.size(); i++) { + + spinBox.clearFocus(); + spinBox.clear(); //clear all + QTest::keyClicks(&spinBox, exprs[i]); + spinBox.clearFocus(); + + double resultParser = KisNumericParser::parseSimpleMathExpr(exprs[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(exprs[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++) { + + spinBox.clearFocus(); + spinBox.clear(); //clear all + QTest::keyClicks(&spinBox, exprs[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()); + + 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::testIntParseNormal() +{ + + QStringList exprs = {"12", + "-12", + "-12.3", + "12.7 - 25", + "12.7", + "12*1.5", + "12/2.5"}; + + KisIntParseSpinBox spinBox; + spinBox.setMaximum(999); + spinBox.setMinimum(-999); + + for(int i = 0; i < exprs.size(); i++){ + + spinBox.clearFocus(); + spinBox.clear(); //clear all + QTest::keyClicks(&spinBox, exprs[i]); + spinBox.clearFocus(); + + int resultParser = KisNumericParser::parseIntegerMathExpr(exprs[i]); + int valueSpinBox = spinBox.value(); + + bool test = resultParser == valueSpinBox; + + QVERIFY2(test, QString("Failed with expression %1, result is %2, value is %3") + .arg(exprs[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++) { + + spinBox.clearFocus(); + spinBox.clear(); //clear all + QTest::keyClicks(&spinBox, exprs[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()); + + spinBox.setValue(0); + + QVERIFY2(spinBox.isLastValid(), QString("SpinBox unsable to recover error free state after a value reset.") + .toStdString().c_str()); + + spinBox.setValue(0); + + } + +} + +QTEST_MAIN(KisParseSpinBoxesTest) diff --git a/libs/ui/tests/kis_simplemathparsertest.h b/libs/ui/tests/kis_simplemathparsertest.h new file mode 100644 --- /dev/null +++ b/libs/ui/tests/kis_simplemathparsertest.h @@ -0,0 +1,19 @@ +#ifndef KISSIMPLEMATHPARSERTEST_H +#define KISSIMPLEMATHPARSERTEST_H + +#include + +class KisSimpleMathParserTest : public QObject +{ + Q_OBJECT + +public: + KisSimpleMathParserTest(); + +private Q_SLOTS: + void testDoubleComputation(); + void testIntComputation(); + void testIntFlooring(); +}; + +#endif // KISSIMPLEMATHPARSERTEST_H diff --git a/libs/ui/tests/kis_simplemathparsertest.cpp b/libs/ui/tests/kis_simplemathparsertest.cpp new file mode 100644 --- /dev/null +++ b/libs/ui/tests/kis_simplemathparsertest.cpp @@ -0,0 +1,132 @@ +#include "kis_simplemathparsertest.h" + +#include "kis_numparser.h" + +#include +#include + +KisSimpleMathParserTest::KisSimpleMathParserTest() : QObject() +{ + +} + +void KisSimpleMathParserTest::testDoubleComputation() +{ + + QStringList exprs = {"1", + "2 + 3.4", + "2 + -3.4", + "2 - -3.4", + "5-2", + "7 + 2 - 5", + "4.6 * 2 + 13", + "4.6 / 2 + 3*3", + "4.6 / 0.0 + 3*3", + "-4.6 / 0.0 + 3*3", + "-4.6 / -0.0 + 3*3", + "4.6 / -0.0 + 3*3", + "0.0 / 0.0 + 3*3", + "2^3 - 4 * 1.5", + "2^3.0 - 4 * 1.5", + "cos(1)*2", + "-cos(1)*2", + "cos(1)^3*2", + "cos(1)^3.0*2", + "cos(1)*2 + sin(3)/2", + "cos(acos(-1)+1*3^2.0)*2 + sin(3)/2"}; + + QVector expected = {1, + 2 + 3.4, + 2 + -3.4, + 2 - -3.4, + 5-2, + 7 + 2 - 5, + 4.6 * 2 + 13, + 4.6 / 2 + 3*3, + 4.6 / 0.0 + 3*3, + -4.6 / 0.0 + 3*3, + -4.6 / -0.0 + 3*3, + 4.6 / -0.0 + 3*3, + 0.0 / 0.0 + 3*3, + qPow(2,3) - 4 * 1.5, + qPow(2,3.0) - 4 * 1.5, + qCos(1.0/180*qAcos(-1))*2, + -qCos(1.0/180*qAcos(-1))*2, + qPow(qCos(1.0/180*qAcos(-1)),3)*2, + qPow(qCos(1.0/180*qAcos(-1)),3.0)*2, + qCos(1.0/180*qAcos(-1))*2 + qSin(3.0/180*qAcos(-1))/2, + qCos((qAcos(-1.0)*180/qAcos(-1)+1*qPow(3,2.0))/180*qAcos(-1))*2 + qSin(3.0/180*qAcos(-1))/2}; + + for (int i = 0; i < expected.size(); i++) { + + double result = KisNumericParser::parseSimpleMathExpr(exprs[i]); + + bool test = result == expected[i] || qAbs(result - expected[i]) < 1e-12 || (isnan(result) && isnan(expected[i])); + + QVERIFY2(test, QString("Failed when %1 should equal %2 but evaluated to %3.").arg(exprs[i]).arg(expected[i]).arg(result).toStdString().c_str()); + } +} + +void KisSimpleMathParserTest::testIntComputation() +{ + + QStringList exprs = {"1", + "2 + 3", + "2 + -3", + "2 - -3", + "5-2", + "7 + 2 - 5", + "4/3", + "12/3", + "4*3"}; + + QVector expected = {1, + 2 + 3, + 2 + -3, + 2 - -3, + 5-2, + 7 + 2 - 5, + qRound(4.0/3.0), + qRound(12.0/3.0), + 4*3}; + + for (int i = 0; i < expected.size(); i++) { + + int result = KisNumericParser::parseIntegerMathExpr(exprs[i]); + + QCOMPARE(result, expected[i]); + } +} + +void KisSimpleMathParserTest::testIntFlooring() +{ + + QStringList exprs = {"4.5", + "-4.5", + "3.5 + 4.5", + "2.8 - -3.5", + "4.5/2.9", + "7.6*3.2", + "7.6*3.2 + 4.5" + }; + + QVector expected = {qRound(4.5), + qRound(-4.5), + qRound(3.5 + 4.5), + qRound(2.8 - -3.5), + qRound(4.5/2.9), + qRound(7.6*3.2), + qRound(7.6*3.2 + 4.5) + }; + + for (int i = 0; i < expected.size(); i++) { + + int result = KisNumericParser::parseIntegerMathExpr(exprs[i]); + + QCOMPARE(result, expected[i]); + } + +} + +QTEST_APPLESS_MAIN(KisSimpleMathParserTest) + diff --git a/libs/ui/utils/kis_numparser.h b/libs/ui/utils/kis_numparser.h new file mode 100644 --- /dev/null +++ b/libs/ui/utils/kis_numparser.h @@ -0,0 +1,42 @@ +/* + * 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 KIS_NUMPARSER_H +#define KIS_NUMPARSER_H + +#include + +#include "kritaui_export.h" + +/*! + * \brief the namespace contains functions to transform math expression written as QString in numbers. + * + * Computation is done in a recursive way, maybe not the most efficient way compared to infix to postfix conversion before parsing. + * (TODO: look if it need to be changed). + */ +namespace KisNumericParser { + + //! \brief parse an expression to a double. + KRITAUI_EXPORT double parseSimpleMathExpr(QString const& expr, bool* noProblem = 0); + + //! \brief parse an expression to an int. + KRITAUI_EXPORT int parseIntegerMathExpr(QString const& expr, bool* noProblem = 0); +} + +#endif // KIS_NUMPARSER_H + diff --git a/libs/ui/utils/kis_numparser.cpp b/libs/ui/utils/kis_numparser.cpp new file mode 100644 --- /dev/null +++ b/libs/ui/utils/kis_numparser.cpp @@ -0,0 +1,598 @@ +/* + * 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_numparser.h" + +//#include +#include +#include +#include +#include +#include + +using namespace std; + +const QVector opLevel1 = {'+', '-'}; +const QVector opLevel2 = {'*', '/'}; + +const QStringList supportedFuncs = {"", "cos", "sin", "tan", "acos", "asin", "atan", "exp", "ln", "log10", "abs"}; + +const QRegExp funcExpr("(-)?([a-zA-Z]*)?\\((.+)\\)"); +const QRegExp numberExpr("(-)?([0-9]+\\.?[0-9]*(e[0-9]*)?)"); + +const QRegExp funcExprInteger("(-)?\\((.+)\\)"); +const QRegExp integerExpr("(-)?([0-9]+)"); + +//double functions +double treatFuncs(QString const& expr, bool & noProblem); +double treatLevel1(QString const& expr, bool & noProblem); +double treatLevel2(QString const& expr, bool & noProblem); +double treatLevel3(QString const& expr, bool & noProblem); + +//int functions +double treatLevel1Int(QString const& expr, bool & noProblem); +double treatLevel2Int(QString const& expr, bool & noProblem); +double treatFuncsInt(QString const& expr, bool & noProblem); + +namespace KisNumericParser { + +/*! + * \param expr the expression to parse + * \param noProblem if provided, the value pointed to will be se to true is no problem appeared, false otherwise. + * \return the numerical value the expression eval to (or 0 in case of error). + */ +double parseSimpleMathExpr(const QString &expr, bool *noProblem) +{ + + bool ok = true; //intermediate variable to pass by reference to the sublevel parser (if no pointer is provided). + + //then go down each 3 levels of operation priority. + if (noProblem != nullptr) { + return treatLevel1(expr, *noProblem); + } + + return treatLevel1(expr, ok); + +} + +/*! + * \param expr the expression to parse + * \param noProblem if provided, the value pointed to will be se to true is no problem appeared, false otherwise. + * \return the numerical value the expression eval to (or 0 in case of error). + */ +int parseIntegerMathExpr(QString const& expr, bool* noProblem) +{ + + bool ok = true; //intermediate variable to pass by reference to the sublevel parser (if no pointer is provided). + + if (noProblem != nullptr) { + return qRound(treatLevel1Int(expr, *noProblem)); + } + + return qRound(treatLevel1Int(expr, ok)); + +} + +} //namespace KisNumericParser. + + +//intermediate functions + +/*! + * \brief cutLevel1 cut an expression into many subparts using the level1 operations (+-) outside of parenthesis as separator. Return some results by reference. + * \param expr The expression to cut. + * \param readyToTreat A reference to a string list to hold the subparts. + * \param op A list of operations stored as char ('+' and '-') and returned by reference. + * \param noProblem A reference to a bool, set to true if there was no problem, false otherwise. + * + * The function won't cut the expression if the + or - operation is nested within parenthesis. The subexpression in the parenthesis will be treated recursivly later on. + */ +inline void cutLevel1(QString const& expr, QStringList & readyToTreat, QVector & op, bool & noProblem) +{ + + readyToTreat.clear(); + op.clear(); + + int subCount = 0; + int lastPos = 0; + + bool lastMetIsNumber = false; + + for(int i = 0; i < expr.size(); i++){ + + if (expr.at(i) == '(') { + subCount++; + } + + if (expr.at(i) == ')') { + subCount--; + } + + if (subCount < 0) { + noProblem = false; + return; + } + + if( (expr.at(i) == '+' || expr.at(i) == '-') && + subCount == 0) { + + if (expr.at(i) == '-' && + i < expr.size()-1) { + + bool cond = !expr.at(i+1).isSpace(); + + if (cond && !lastMetIsNumber) { + continue; + } + + } + + readyToTreat.push_back(expr.mid(lastPos, i-lastPos).trimmed()); + lastPos = i+1; + op.push_back(expr.at(i).toLatin1()); + + } + + if (expr.at(i).isDigit()) { + lastMetIsNumber = true; + } else if (expr.at(i) != '.' && + !expr.at(i).isSpace()) { + lastMetIsNumber = false; + } + } + + readyToTreat.push_back(expr.mid(lastPos).trimmed()); + +} + +/*! + * \brief cutLeve2 cut an expression into many subparts using the level2 operations (* and /) outside of parenthesis as separator. Return some results by reference. + * \param expr The expression to cut. + * \param readyToTreat A reference to a string list to hold the subparts. + * \param op A list of operations stored as char ('*' and '/') and returned by reference. + * \param noProblem A reference to a bool, set to true if there was no problem, false otherwise. + * + * The function won't cut the expression if the * or / operation is nested within parenthesis. The subexpression in the parenthesis will be treated recursivly later on. + */ +inline void cutLevel2(QString const& expr, QStringList & readyToTreat, QVector & op, bool & noProblem) +{ + + readyToTreat.clear(); + op.clear(); + + int subCount = 0; + int lastPos = 0; + + for (int i = 0; i < expr.size(); i++) { + + if (expr.at(i) == '(') { + subCount++; + } + + if (expr.at(i) == ')') { + subCount--; + } + + if (subCount < 0) { + noProblem = false; + return; + } + + if ( (expr.at(i) == '*' || expr.at(i) == '/') && + subCount == 0) { + + readyToTreat.push_back(expr.mid(lastPos, i-lastPos).trimmed()); + lastPos = i+1; + op.push_back(expr.at(i).toLatin1()); + + } + } + + readyToTreat.push_back(expr.mid(lastPos).trimmed()); +} + +/*! + * \brief treatLevel1 treat an expression at the first level of recursion. + * \param expr The expression to treat. + * \param noProblem A reference to a bool set to true if no problem happened, false otherwise. + * \return The value of the parsed expression or subexpression or 0 in case of error. + */ +double treatLevel1(const QString &expr, bool & noProblem) +{ + + noProblem = true; + + QStringList readyToTreat; + QVector op; + + cutLevel1(expr, readyToTreat, op, noProblem); + if (!noProblem) { + return 0.0; + } + + if (readyToTreat.contains("")) { + noProblem = false; + return 0.0; + } + + if (op.size() != readyToTreat.size()-1) { + noProblem = false; + return 0.0; + } + + double result = 0.0; + + for (int i = 0; i < readyToTreat.size(); i++) { + + if (i == 0) { + result += treatLevel2(readyToTreat[i], noProblem); + } else { + if (op[i-1] == '+') { + result += treatLevel2(readyToTreat[i], noProblem); + } else if (op[i-1] == '-') { + result -= treatLevel2(readyToTreat[i], noProblem); + } + } + + if (noProblem == false) { + return 0.0; + } + } + + return result; + +} + +/*! + * \brief treatLevel2 treat a subexpression at the second level of recursion. + * \param expr The subexpression to treat. + * \param noProblem A reference to a bool set to true if no problem happened, false otherwise. + * \return The value of the parsed subexpression or 0 in case of error. + * + * The expression should not contain first level operations not nested in parenthesis. + */ +double treatLevel2(QString const& expr, bool & noProblem) +{ + + noProblem = true; + + QStringList readyToTreat; + QVector op; + + cutLevel2(expr, readyToTreat, op, noProblem); + if (!noProblem) { + return 0.0; + } + + if (readyToTreat.contains("")) { + noProblem = false; + return 0.0; + } + + if (op.size() != readyToTreat.size()-1) { + noProblem = false; + return 0.0; + } + + double result = 0.0; + + for (int i = 0; i < readyToTreat.size(); i++) { + + if (i == 0) { + result += treatLevel3(readyToTreat[i], noProblem); + } else { + if (op[i-1] == '*') { + result *= treatLevel3(readyToTreat[i], noProblem); + } else if(op[i-1] == '/') { + //may become infinity or NAN. + result /= treatLevel3(readyToTreat[i], noProblem); + } + } + + if (noProblem == false) { + return 0.0; + } + } + + return result; +} + +/*! + * \brief treatLevel3 treat a subexpression at the third level of recursion. + * \param expr The subexpression to treat. + * \param noProblem A reference to a bool set to true if no problem happened, false otherwise. + * \return The value of the parsed subexpression or 0 in case of error. + * + * The expression should not contain first or second level operations not nested in parenthesis. + */ +double treatLevel3(const QString &expr, bool & noProblem) +{ + + noProblem = true; + + int indexPower = -1; + int indexCount = 0; + int subLevels = 0; + + for (int i = 0; i < expr.size(); i++) { + if (expr.at(i) == '(') { + subLevels++; + } else if(expr.at(i) == ')') { + subLevels--; + if (subLevels < 0) { + noProblem = false; + return 0.0; + } + } else if (expr.at(i) == '^') { + if (subLevels == 0) { + indexPower = i; + indexCount++; + } + } + } + + if (indexCount > 1) { + noProblem = false; + return 0.0; + } + + if (indexPower > -1) { + + QStringList subExprs = expr.split('^'); + + bool noProb1 = true; + bool noProb2 = true; + + double base = treatFuncs(subExprs[0], noProb1); + double power = treatFuncs(subExprs[1], noProb2); + + return qPow(base, power); + + } else { + return treatFuncs(expr, noProblem); + } + + noProblem = false; + return 0.0; + +} + +/*! + * \brief treatFuncs treat the last level of recursion: parenthesis and functions. + * \param expr The expression to parse. + * \param noProblem A reference to a bool set to true if no problem happened, false otherwise. + * \return The value of the parsed subexpression or 0 in case of error. + * + * The expression should not contain operators not nested anymore. The subexpressions within parenthesis will be treated by recalling the level 1 function. + */ +double treatFuncs(QString const& expr, bool & noProblem) +{ + + noProblem = true; + + QRegExp funcExp = funcExpr; //copy the expression in the current execution stack, to avoid errors for example when multiple thread call this function. + QRegExp numExp = numberExpr; + + if (funcExp.exactMatch(expr.trimmed())) { + + int sign = funcExp.capturedTexts()[1].isEmpty() ? 1 : -1; + QString func = funcExp.capturedTexts()[2].toLower(); + QString subExpr = funcExp.capturedTexts()[3]; + + double val = treatLevel1(subExpr, noProblem); + + if (!noProblem) { + return 0.0; + } + + if (func.isEmpty()) { + return sign*val; + } + + if (!supportedFuncs.contains(func)) { + noProblem = false; + return 0.0; + } + + //trigonometry is done in degree + if (func == "cos") { + val = qCos(val/180*qAcos(-1)); + } else if (func == "sin") { + val = qSin(val/180*qAcos(-1)); + } else if (func == "tan") { + val = qTan(val/180*qAcos(-1)); + } else if(func == "acos") { + val = qAcos(val)*180/qAcos(-1); + } else if (func == "asin") { + val = qAsin(val)*180/qAcos(-1); + } else if (func == "atan") { + val = qAtan(val)*180/qAcos(-1); + } else if (func == "exp") { + val = qExp(val); + } else if (func == "ln") { + val = qLn(val); + } else if (func == "log10") { + val = qLn(val)/qLn(10.0); + } else if (func == "abs") { + val = qAbs(val); + } + + return sign*val; + } else if(numExp.exactMatch(expr.trimmed())) { + return expr.toDouble(&noProblem); + } + + noProblem = false; + return 0.0; + +} + +//int functions +/*! + * \brief treatLevel1 treat an expression at the first level of recursion. + * \param expr The expression to treat. + * \param noProblem A reference to a bool set to true if no problem happened, false otherwise. + * \return The value of the parsed expression or subexpression or 0 in case of error. + */ +double treatLevel1Int(QString const& expr, bool & noProblem) +{ + + noProblem = true; + + QStringList readyToTreat; + QVector op; + + cutLevel1(expr, readyToTreat, op, noProblem); + if (!noProblem) { + return 0.0; + } + + if (readyToTreat.contains("")) { + noProblem = false; + return 0; + } + + if (op.size() != readyToTreat.size()-1) { + noProblem = false; + return 0; + } + + double result = 0; + + for (int i = 0; i < readyToTreat.size(); i++) { + + if (i == 0) { + result += treatLevel2Int(readyToTreat[i], noProblem); + } else { + if (op[i-1] == '+') { + result += treatLevel2Int(readyToTreat[i], noProblem); + } else if(op[i-1] == '-') { + result -= treatLevel2Int(readyToTreat[i], noProblem); + } + } + + if (noProblem == false) { + return 0; + } + } + + return result; + +} + +/*! + * \brief treatLevel2 treat a subexpression at the second level of recursion. + * \param expr The subexpression to treat. + * \param noProblem A reference to a bool set to true if no problem happened, false otherwise. + * \return The value of the parsed subexpression or 0 in case of error. + * + * The expression should not contain first level operations not nested in parenthesis. + */ +double treatLevel2Int(const QString &expr, bool &noProblem) +{ + + noProblem = true; + + QStringList readyToTreat; + QVector op; + + cutLevel2(expr, readyToTreat, op, noProblem); + if (!noProblem) { + return 0.0; + } + + if (readyToTreat.contains("")) { + noProblem = false; + return 0; + } + + if (op.size() != readyToTreat.size()-1) { + noProblem = false; + return 0; + } + + double result = 0; + + for (int i = 0; i < readyToTreat.size(); i++) { + + if (i == 0) { + result += treatFuncsInt(readyToTreat[i], noProblem); + } else { + if (op[i-1] == '*') { + result *= treatFuncsInt(readyToTreat[i], noProblem); + } else if(op[i-1] == '/') { + int value = treatFuncsInt(readyToTreat[i], noProblem); + + //int int airthmetic it's impossible to divide by 0. + if (value == 0) { + noProblem = false; + return 0; + } + + result /= value; + } + } + + if (noProblem == false) { + return 0; + } + } + + return result; + +} + +/*! + * \brief treatFuncs treat the last level of recursion: parenthesis + * \param expr The expression to parse. + * \param noProblem A reference to a bool set to true if no problem happened, false otherwise. + * \return The value of the parsed subexpression or 0 in case of error. + * + * The expression should not contain operators not nested anymore. The subexpressions within parenthesis will be treated by recalling the level 1 function. + */ +double treatFuncsInt(QString const& expr, bool & noProblem) +{ + + noProblem = true; + + QRegExp funcExpInteger = funcExprInteger; + QRegExp integerExp = integerExpr; + QRegExp numberExp = numberExpr; + + if (funcExpInteger.exactMatch(expr.trimmed())) { + + int sign = funcExpInteger.capturedTexts()[1].isEmpty() ? 1 : -1; + QString subExpr = funcExpInteger.capturedTexts()[2]; + + double val = treatLevel1Int(subExpr, noProblem); + + if (!noProblem) { + return 0; + } + + return sign*val; + + } else if(numberExp.exactMatch(expr.trimmed())) { + double value = QVariant(expr).toDouble(&noProblem); + return value; + } + + noProblem = false; + return 0; + +} diff --git a/libs/ui/widgets/kis_doubleparsespinbox.h b/libs/ui/widgets/kis_doubleparsespinbox.h new file mode 100644 --- /dev/null +++ b/libs/ui/widgets/kis_doubleparsespinbox.h @@ -0,0 +1,76 @@ +/* + * 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 KISDOUBLEPARSESPINBOX_H +#define KISDOUBLEPARSESPINBOX_H + +#include + +#include + +class QLabel; + +/*! + * \brief The KisDoubleParseSpinBox class is a cleverer doubleSpinBox, able to parse arithmetic expressions. + * + * Use this spinbox instead of the basic one from Qt if you want it to be able to parse arithmetic expressions. + */ +class KRITAUI_EXPORT KisDoubleParseSpinBox : public QDoubleSpinBox +{ + + Q_OBJECT +public: + KisDoubleParseSpinBox(QWidget* parent = 0); + ~KisDoubleParseSpinBox(); + + virtual double valueFromText(const QString & text) const; + virtual QString textFromValue(double val) const; + virtual QValidator::State validate ( QString & input, int & pos ) const; + + virtual void stepBy(int steps); + + void setValue(double value); //polymorphism won't work directly, we use a signal/slot hack to do so but if signals are disabled this function will still be useful. + + bool isLastValid() const{ return _isLastValid; } + +Q_SIGNALS: + + //! \brief signal emmitted when the last parsed expression create an error. + void errorWhileParsing(QString expr) const; + //! \brief signal emmitted when the last parsed expression is valid. + void noMoreParsingError() const; + +public Q_SLOTS: + + //! \brief useful to let the widget change it's stylesheet when an error occured in the last expression. + void setErrorStyle(); + //! \brief useful to let the widget reset it's stylesheet when there's no more error. + void clearErrorStyle(); + //! \brief say the widget to return to an error free state. + void clearError(); + +protected: + + mutable QString* _lastExprParsed; + mutable bool _isLastValid; + mutable double _oldValue; + + QLabel* _warningIcon; +}; + +#endif // KISDOUBLEPARSESPINBOX_H diff --git a/libs/ui/widgets/kis_doubleparsespinbox.cpp b/libs/ui/widgets/kis_doubleparsespinbox.cpp new file mode 100644 --- /dev/null +++ b/libs/ui/widgets/kis_doubleparsespinbox.cpp @@ -0,0 +1,164 @@ +/* + * 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 = KisNumericParser::parseSimpleMathExpr(text, &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.h b/libs/ui/widgets/kis_intparsespinbox.h new file mode 100644 --- /dev/null +++ b/libs/ui/widgets/kis_intparsespinbox.h @@ -0,0 +1,76 @@ +/* + * 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 KISINTPARSESPINBOX_H +#define KISINTPARSESPINBOX_H + +#include + +#include "kritaui_export.h" + +class QLabel; + +/*! + * \brief The KisDoubleParseSpinBox class is a cleverer doubleSpinBox, able to parse arithmetic expressions. + * + * Use this spinbox instead of the basic one from Qt if you want it to be able to parse arithmetic expressions. + */ +class KRITAUI_EXPORT KisIntParseSpinBox : public QSpinBox +{ + Q_OBJECT + +public: + KisIntParseSpinBox(QWidget *parent = 0); + ~KisIntParseSpinBox(); + + virtual int valueFromText(const QString & text) const; + virtual QString textFromValue(int val) const; + virtual QValidator::State validate ( QString & input, int & pos ) const; + + virtual void stepBy(int steps); + + void setValue(int val); //polymorphism won't work directly, we use a signal/slot hack to do so but if signals are disabled this function will still be useful. + + bool isLastValid() const{ return _isLastValid; } + +Q_SIGNALS: + + //! \brief signal emmitted when the last parsed expression create an error. + void errorWhileParsing(QString expr) const; + //! \brief signal emmitted when the last parsed expression is valid. + void noMoreParsingError() const; + +public Q_SLOTS: + + //! \brief useful to let the widget change it's stylesheet when an error occured in the last expression. + void setErrorStyle(); + //! \brief useful to let the widget reset it's stylesheet when there's no more error. + void clearErrorStyle(); + //! \brief say the widget to return to an error free state. + void clearError(); + +protected: + + mutable QString* _lastExprParsed; + mutable bool _isLastValid; + mutable int _oldVal; //store the last correctly evaluated value. + + QLabel* _warningIcon; +}; + +#endif // KISINTPARSESPINBOX_H diff --git a/libs/ui/widgets/kis_intparsespinbox.cpp b/libs/ui/widgets/kis_intparsespinbox.cpp new file mode 100644 --- /dev/null +++ b/libs/ui/widgets/kis_intparsespinbox.cpp @@ -0,0 +1,173 @@ +/* + * 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 = KisNumericParser::parseIntegerMathExpr(text, &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(); +} diff --git a/pics/16_dark_warning.svg b/pics/16_dark_warning.svg new file mode 100644 --- /dev/null +++ b/pics/16_dark_warning.svg @@ -0,0 +1,89 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/pics/16_light_warning.svg b/pics/16_light_warning.svg new file mode 100644 --- /dev/null +++ b/pics/16_light_warning.svg @@ -0,0 +1,89 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/pics/icons.qrc b/pics/icons.qrc --- a/pics/icons.qrc +++ b/pics/icons.qrc @@ -1,304 +1,305 @@ - - - - 16_dark_align-horizontal-center.svg - 22_dark_align-horizontal-center.svg - 24_dark_align-horizontal-center.svg - 16_light_align-horizontal-center.svg - 22_light_align-horizontal-center.svg - 24_light_align-horizontal-center.svg - 16_dark_align-horizontal-left.svg - 22_dark_align-horizontal-left.svg - 24_dark_align-horizontal-left.svg - 16_light_align-horizontal-left.svg - 22_light_align-horizontal-left.svg - 24_light_align-horizontal-left.svg - 16_dark_align-horizontal-right.svg - 22_dark_align-horizontal-right.svg - 24_dark_align-horizontal-right.svg - 16_light_align-horizontal-right.svg - 22_light_align-horizontal-right.svg - 24_light_align-horizontal-right.svg - 16_dark_align-vertical-bottom.svg - 22_dark_align-vertical-bottom.svg - 24_dark_align-vertical-bottom.svg - 16_light_align-vertical-bottom.svg - 22_light_align-vertical-bottom.svg - 24_light_align-vertical-bottom.svg - 16_dark_align-vertical-center.svg - 22_dark_align-vertical-center.svg - 24_dark_align-vertical-center.svg - 16_light_align-vertical-center.svg - 22_light_align-vertical-center.svg - 24_light_align-vertical-center.svg - 16_dark_align-vertical-top.svg - 22_dark_align-vertical-top.svg - 24_dark_align-vertical-top.svg - 16_light_align-vertical-top.svg - 22_light_align-vertical-top.svg - 24_light_align-vertical-top.svg - 16_dark_application-x-krita.svg - 22_dark_application-x-krita.svg - 32_dark_application-x-krita.svg - 64_dark_application-x-krita.svg - 16_light_application-x-krita.svg - 22_light_application-x-krita.svg - 32_light_application-x-krita.svg - 64_light_application-x-krita.svg - 16_dark_application-x-wmf.svg - 22_dark_application-x-wmf.svg - 32_dark_application-x-wmf.svg - 64_dark_application-x-wmf.svg - 16_light_application-x-wmf.svg - 22_light_application-x-wmf.svg - 32_light_application-x-wmf.svg - 64_light_application-x-wmf.svg - calligrakrita.png - 16_dark_character-set.svg - 22_dark_character-set.svg - 24_dark_character-set.svg - 16_light_character-set.svg - 22_light_character-set.svg - 24_light_character-set.svg - 16_dark_draw-arrow-back.svg - 22_dark_draw-arrow-back.svg - 24_dark_draw-arrow-back.svg - 16_light_draw-arrow-back.svg - 22_light_draw-arrow-back.svg - 24_light_draw-arrow-back.svg - 16_dark_draw-arrow-down.svg - 22_dark_draw-arrow-down.svg - 24_dark_draw-arrow-down.svg - 16_light_draw-arrow-down.svg - 22_light_draw-arrow-down.svg - 24_light_draw-arrow-down.svg - 16_dark_draw-arrow-forward.svg - 22_dark_draw-arrow-forward.svg - 24_dark_draw-arrow-forward.svg - 16_light_draw-arrow-forward.svg - 22_light_draw-arrow-forward.svg - 24_light_draw-arrow-forward.svg - 16_dark_draw-arrow-up.svg - 22_dark_draw-arrow-up.svg - 24_dark_draw-arrow-up.svg - 16_light_draw-arrow-up.svg - 22_light_draw-arrow-up.svg - 24_light_draw-arrow-up.svg - 16_dark_draw-freehand.svg - 22_dark_draw-freehand.svg - 24_dark_draw-freehand.svg - 16_light_draw-freehand.svg - 22_light_draw-freehand.svg - 24_light_draw-freehand.svg - 16_dark_edit-rename.svg - 22_dark_edit-rename.svg - 24_dark_edit-rename.svg - 16_light_edit-rename.svg - 22_light_edit-rename.svg - 24_light_edit-rename.svg - 16_dark_edit-select-all.svg - 22_dark_edit-select-all.svg - 24_dark_edit-select-all.svg - 16_light_edit-select-all.svg - 22_light_edit-select-all.svg - 24_light_edit-select-all.svg - 16_dark_format-indent-less.svg - 22_dark_format-indent-less.svg - 24_dark_format-indent-less.svg - 16_light_format-indent-less.svg - 22_light_format-indent-less.svg - 24_light_format-indent-less.svg - 16_dark_format-indent-more.svg - 22_dark_format-indent-more.svg - 24_dark_format-indent-more.svg - 16_light_format-indent-more.svg - 22_light_format-indent-more.svg - 24_light_format-indent-more.svg - 16_dark_format-justify-center.svg - 22_dark_format-justify-center.svg - 24_dark_format-justify-center.svg - 16_light_format-justify-center.svg - 22_light_format-justify-center.svg - 24_light_format-justify-center.svg - 16_dark_format-justify-fill.svg - 22_dark_format-justify-fill.svg - 24_dark_format-justify-fill.svg - 16_light_format-justify-fill.svg - 22_light_format-justify-fill.svg - 24_light_format-justify-fill.svg - 16_dark_format-justify-left.svg - 22_dark_format-justify-left.svg - 24_dark_format-justify-left.svg - 16_light_format-justify-left.svg - 22_light_format-justify-left.svg - 24_light_format-justify-left.svg - 16_dark_format-justify-right.svg - 22_dark_format-justify-right.svg - 24_dark_format-justify-right.svg - 16_light_format-justify-right.svg - 22_light_format-justify-right.svg - 24_light_format-justify-right.svg - 16_dark_format-list-ordered.svg - 22_dark_format-list-ordered.svg - 24_dark_format-list-ordered.svg - 16_light_format-list-ordered.svg - 22_light_format-list-ordered.svg - 24_light_format-list-ordered.svg - 16_dark_format-stroke-color.svg - 22_dark_format-stroke-color.svg - 24_dark_format-stroke-color.svg - 16_light_format-stroke-color.svg - 22_light_format-stroke-color.svg - 24_light_format-stroke-color.svg - 16_dark_format-text-bold.svg - 22_dark_format-text-bold.svg - 24_dark_format-text-bold.svg - 16_light_format-text-bold.svg - 22_light_format-text-bold.svg - 24_light_format-text-bold.svg - 16_dark_format-text-color.svg - 22_dark_format-text-color.svg - 24_dark_format-text-color.svg - 16_light_format-text-color.svg - 22_light_format-text-color.svg - 24_light_format-text-color.svg - 16_dark_format-text-direction-rtl.svg - 22_dark_format-text-direction-rtl.svg - 24_dark_format-text-direction-rtl.svg - 16_light_format-text-direction-rtl.svg - 22_light_format-text-direction-rtl.svg - 24_light_format-text-direction-rtl.svg - 16_dark_format-text-italic.svg - 22_dark_format-text-italic.svg - 24_dark_format-text-italic.svg - 16_light_format-text-italic.svg - 22_light_format-text-italic.svg - 24_light_format-text-italic.svg - 16_dark_format-text-strikethrough.svg - 22_dark_format-text-strikethrough.svg - 24_dark_format-text-strikethrough.svg - 16_light_format-text-strikethrough.svg - 22_light_format-text-strikethrough.svg - 24_light_format-text-strikethrough.svg - 16_dark_format-text-subscript.svg - 22_dark_format-text-subscript.svg - 24_dark_format-text-subscript.svg - 16_light_format-text-subscript.svg - 22_light_format-text-subscript.svg - 24_light_format-text-subscript.svg - 16_dark_format-text-superscript.svg - 22_dark_format-text-superscript.svg - 24_dark_format-text-superscript.svg - 16_light_format-text-superscript.svg - 22_light_format-text-superscript.svg - 24_light_format-text-superscript.svg - 16_dark_format-text-underline.svg - 22_dark_format-text-underline.svg - 24_dark_format-text-underline.svg - 16_light_format-text-underline.svg - 22_light_format-text-underline.svg - 24_light_format-text-underline.svg - 16_dark_insert-text.svg - 22_dark_insert-text.svg - 24_dark_insert-text.svg - 16_light_insert-text.svg - 22_light_insert-text.svg - 24_light_insert-text.svg - 48_dark_klipper.svg - 48_light_klipper.svg - 16_dark_list-remove.svg - 22_dark_list-remove.svg - 24_dark_list-remove.svg - 16_light_list-remove.svg - 22_light_list-remove.svg - 24_light_list-remove.svg - 16_dark_media-floppy.svg - 22_dark_media-floppy.svg - 16_light_media-floppy.svg - 22_light_media-floppy.svg - 16_dark_object-align-horizontal-center-calligra.svg - 22_dark_object-align-horizontal-center-calligra.svg - 16_light_object-align-horizontal-center-calligra.svg - 22_light_object-align-horizontal-center-calligra.svg - 16_dark_object-align-horizontal-left-calligra.svg - 22_dark_object-align-horizontal-left-calligra.svg - 16_light_object-align-horizontal-left-calligra.svg - 22_light_object-align-horizontal-left-calligra.svg - 16_dark_object-align-vertical-top-calligra.svg - 22_dark_object-align-vertical-top-calligra.svg - 16_light_object-align-vertical-top-calligra.svg - 22_light_object-align-vertical-top-calligra.svg - 16_dark_object-group-calligra.svg - 22_dark_object-group-calligra.svg - 16_light_object-group-calligra.svg - 22_light_object-group-calligra.svg - 16_dark_shape-choose.svg - 22_dark_shape-choose.svg - 16_light_shape-choose.svg - 22_light_shape-choose.svg - 16_dark_snap-extension.svg - 22_dark_snap-extension.svg - 24_dark_snap-extension.svg - 16_light_snap-extension.svg - 22_light_snap-extension.svg - 24_light_snap-extension.svg - 16_dark_snap-guideline.svg - 22_dark_snap-guideline.svg - 24_dark_snap-guideline.svg - 16_light_snap-guideline.svg - 22_light_snap-guideline.svg - 24_light_snap-guideline.svg - 16_dark_snap-intersection.svg - 22_dark_snap-intersection.svg - 24_dark_snap-intersection.svg - 16_light_snap-intersection.svg - 22_light_snap-intersection.svg - 24_light_snap-intersection.svg - 16_dark_snap-node.svg - 22_dark_snap-node.svg - 24_dark_snap-node.svg - 16_light_snap-node.svg - 22_light_snap-node.svg - 24_light_snap-node.svg - 22_dark_snap-orthogonal.svg - 24_dark_snap-orthogonal.svg - 22_light_snap-orthogonal.svg - 24_light_snap-orthogonal.svg - 16_dark_split.svg - 22_dark_split.svg - 24_dark_split.svg - 16_light_split.svg - 22_light_split.svg - 24_light_split.svg - 16_dark_tab-close.svg - 22_dark_tab-close.svg - 24_dark_tab-close.svg - 16_light_tab-close.svg - 22_light_tab-close.svg - 24_light_tab-close.svg - 16_dark_tab-new.svg - 22_dark_tab-new.svg - 24_dark_tab-new.svg - 16_light_tab-new.svg - 22_light_tab-new.svg - 24_light_tab-new.svg - 16_dark_transform-move.svg - 22_dark_transform-move.svg - 24_dark_transform-move.svg - 16_light_transform-move.svg - 22_light_transform-move.svg - 24_light_transform-move.svg - 16_dark_trash-empty.svg - 22_dark_trash-empty.svg - 24_dark_trash-empty.svg - 16_light_trash-empty.svg - 22_light_trash-empty.svg - 24_light_trash-empty.svg - 16_dark_zoom-fit-best.svg - 22_dark_zoom-fit-best.svg - 24_dark_zoom-fit-best.svg - 16_light_zoom-fit-best.svg - 22_light_zoom-fit-best.svg - 24_light_zoom-fit-best.svg - + + + 16_dark_align-horizontal-center.svg + 22_dark_align-horizontal-center.svg + 24_dark_align-horizontal-center.svg + 16_light_align-horizontal-center.svg + 22_light_align-horizontal-center.svg + 24_light_align-horizontal-center.svg + 16_dark_align-horizontal-left.svg + 22_dark_align-horizontal-left.svg + 24_dark_align-horizontal-left.svg + 16_light_align-horizontal-left.svg + 22_light_align-horizontal-left.svg + 24_light_align-horizontal-left.svg + 16_dark_align-horizontal-right.svg + 22_dark_align-horizontal-right.svg + 24_dark_align-horizontal-right.svg + 16_light_align-horizontal-right.svg + 22_light_align-horizontal-right.svg + 24_light_align-horizontal-right.svg + 16_dark_align-vertical-bottom.svg + 22_dark_align-vertical-bottom.svg + 24_dark_align-vertical-bottom.svg + 16_light_align-vertical-bottom.svg + 22_light_align-vertical-bottom.svg + 24_light_align-vertical-bottom.svg + 16_dark_align-vertical-center.svg + 22_dark_align-vertical-center.svg + 24_dark_align-vertical-center.svg + 16_light_align-vertical-center.svg + 22_light_align-vertical-center.svg + 24_light_align-vertical-center.svg + 16_dark_align-vertical-top.svg + 22_dark_align-vertical-top.svg + 24_dark_align-vertical-top.svg + 16_light_align-vertical-top.svg + 22_light_align-vertical-top.svg + 24_light_align-vertical-top.svg + 16_dark_application-x-krita.svg + 22_dark_application-x-krita.svg + 32_dark_application-x-krita.svg + 64_dark_application-x-krita.svg + 16_light_application-x-krita.svg + 22_light_application-x-krita.svg + 32_light_application-x-krita.svg + 64_light_application-x-krita.svg + 16_dark_application-x-wmf.svg + 22_dark_application-x-wmf.svg + 32_dark_application-x-wmf.svg + 64_dark_application-x-wmf.svg + 16_light_application-x-wmf.svg + 22_light_application-x-wmf.svg + 32_light_application-x-wmf.svg + 64_light_application-x-wmf.svg + calligrakrita.png + 16_dark_character-set.svg + 22_dark_character-set.svg + 24_dark_character-set.svg + 16_light_character-set.svg + 22_light_character-set.svg + 24_light_character-set.svg + 16_dark_draw-arrow-back.svg + 22_dark_draw-arrow-back.svg + 24_dark_draw-arrow-back.svg + 16_light_draw-arrow-back.svg + 22_light_draw-arrow-back.svg + 24_light_draw-arrow-back.svg + 16_dark_draw-arrow-down.svg + 22_dark_draw-arrow-down.svg + 24_dark_draw-arrow-down.svg + 16_light_draw-arrow-down.svg + 22_light_draw-arrow-down.svg + 24_light_draw-arrow-down.svg + 16_dark_draw-arrow-forward.svg + 22_dark_draw-arrow-forward.svg + 24_dark_draw-arrow-forward.svg + 16_light_draw-arrow-forward.svg + 22_light_draw-arrow-forward.svg + 24_light_draw-arrow-forward.svg + 16_dark_draw-arrow-up.svg + 22_dark_draw-arrow-up.svg + 24_dark_draw-arrow-up.svg + 16_light_draw-arrow-up.svg + 22_light_draw-arrow-up.svg + 24_light_draw-arrow-up.svg + 16_dark_draw-freehand.svg + 22_dark_draw-freehand.svg + 24_dark_draw-freehand.svg + 16_light_draw-freehand.svg + 22_light_draw-freehand.svg + 24_light_draw-freehand.svg + 16_dark_edit-rename.svg + 22_dark_edit-rename.svg + 24_dark_edit-rename.svg + 16_light_edit-rename.svg + 22_light_edit-rename.svg + 24_light_edit-rename.svg + 16_dark_edit-select-all.svg + 22_dark_edit-select-all.svg + 24_dark_edit-select-all.svg + 16_light_edit-select-all.svg + 22_light_edit-select-all.svg + 24_light_edit-select-all.svg + 16_dark_format-indent-less.svg + 22_dark_format-indent-less.svg + 24_dark_format-indent-less.svg + 16_light_format-indent-less.svg + 22_light_format-indent-less.svg + 24_light_format-indent-less.svg + 16_dark_format-indent-more.svg + 22_dark_format-indent-more.svg + 24_dark_format-indent-more.svg + 16_light_format-indent-more.svg + 22_light_format-indent-more.svg + 24_light_format-indent-more.svg + 16_dark_format-justify-center.svg + 22_dark_format-justify-center.svg + 24_dark_format-justify-center.svg + 16_light_format-justify-center.svg + 22_light_format-justify-center.svg + 24_light_format-justify-center.svg + 16_dark_format-justify-fill.svg + 22_dark_format-justify-fill.svg + 24_dark_format-justify-fill.svg + 16_light_format-justify-fill.svg + 22_light_format-justify-fill.svg + 24_light_format-justify-fill.svg + 16_dark_format-justify-left.svg + 22_dark_format-justify-left.svg + 24_dark_format-justify-left.svg + 16_light_format-justify-left.svg + 22_light_format-justify-left.svg + 24_light_format-justify-left.svg + 16_dark_format-justify-right.svg + 22_dark_format-justify-right.svg + 24_dark_format-justify-right.svg + 16_light_format-justify-right.svg + 22_light_format-justify-right.svg + 24_light_format-justify-right.svg + 16_dark_format-list-ordered.svg + 22_dark_format-list-ordered.svg + 24_dark_format-list-ordered.svg + 16_light_format-list-ordered.svg + 22_light_format-list-ordered.svg + 24_light_format-list-ordered.svg + 16_dark_format-stroke-color.svg + 22_dark_format-stroke-color.svg + 24_dark_format-stroke-color.svg + 16_light_format-stroke-color.svg + 22_light_format-stroke-color.svg + 24_light_format-stroke-color.svg + 16_dark_format-text-bold.svg + 22_dark_format-text-bold.svg + 24_dark_format-text-bold.svg + 16_light_format-text-bold.svg + 22_light_format-text-bold.svg + 24_light_format-text-bold.svg + 16_dark_format-text-color.svg + 22_dark_format-text-color.svg + 24_dark_format-text-color.svg + 16_light_format-text-color.svg + 22_light_format-text-color.svg + 24_light_format-text-color.svg + 16_dark_format-text-direction-rtl.svg + 22_dark_format-text-direction-rtl.svg + 24_dark_format-text-direction-rtl.svg + 16_light_format-text-direction-rtl.svg + 22_light_format-text-direction-rtl.svg + 24_light_format-text-direction-rtl.svg + 16_dark_format-text-italic.svg + 22_dark_format-text-italic.svg + 24_dark_format-text-italic.svg + 16_light_format-text-italic.svg + 22_light_format-text-italic.svg + 24_light_format-text-italic.svg + 16_dark_format-text-strikethrough.svg + 22_dark_format-text-strikethrough.svg + 24_dark_format-text-strikethrough.svg + 16_light_format-text-strikethrough.svg + 22_light_format-text-strikethrough.svg + 24_light_format-text-strikethrough.svg + 16_dark_format-text-subscript.svg + 22_dark_format-text-subscript.svg + 24_dark_format-text-subscript.svg + 16_light_format-text-subscript.svg + 22_light_format-text-subscript.svg + 24_light_format-text-subscript.svg + 16_dark_format-text-superscript.svg + 22_dark_format-text-superscript.svg + 24_dark_format-text-superscript.svg + 16_light_format-text-superscript.svg + 22_light_format-text-superscript.svg + 24_light_format-text-superscript.svg + 16_dark_format-text-underline.svg + 22_dark_format-text-underline.svg + 24_dark_format-text-underline.svg + 16_light_format-text-underline.svg + 22_light_format-text-underline.svg + 24_light_format-text-underline.svg + 16_dark_insert-text.svg + 22_dark_insert-text.svg + 24_dark_insert-text.svg + 16_light_insert-text.svg + 22_light_insert-text.svg + 24_light_insert-text.svg + 48_dark_klipper.svg + 48_light_klipper.svg + 16_dark_list-remove.svg + 22_dark_list-remove.svg + 24_dark_list-remove.svg + 16_light_list-remove.svg + 22_light_list-remove.svg + 24_light_list-remove.svg + 16_dark_media-floppy.svg + 22_dark_media-floppy.svg + 16_light_media-floppy.svg + 22_light_media-floppy.svg + 16_dark_object-align-horizontal-center-calligra.svg + 22_dark_object-align-horizontal-center-calligra.svg + 16_light_object-align-horizontal-center-calligra.svg + 22_light_object-align-horizontal-center-calligra.svg + 16_dark_object-align-horizontal-left-calligra.svg + 22_dark_object-align-horizontal-left-calligra.svg + 16_light_object-align-horizontal-left-calligra.svg + 22_light_object-align-horizontal-left-calligra.svg + 16_dark_object-align-vertical-top-calligra.svg + 22_dark_object-align-vertical-top-calligra.svg + 16_light_object-align-vertical-top-calligra.svg + 22_light_object-align-vertical-top-calligra.svg + 16_dark_object-group-calligra.svg + 22_dark_object-group-calligra.svg + 16_light_object-group-calligra.svg + 22_light_object-group-calligra.svg + 16_dark_shape-choose.svg + 22_dark_shape-choose.svg + 16_light_shape-choose.svg + 22_light_shape-choose.svg + 16_dark_snap-extension.svg + 22_dark_snap-extension.svg + 24_dark_snap-extension.svg + 16_light_snap-extension.svg + 22_light_snap-extension.svg + 24_light_snap-extension.svg + 16_dark_snap-guideline.svg + 22_dark_snap-guideline.svg + 24_dark_snap-guideline.svg + 16_light_snap-guideline.svg + 22_light_snap-guideline.svg + 24_light_snap-guideline.svg + 16_dark_snap-intersection.svg + 22_dark_snap-intersection.svg + 24_dark_snap-intersection.svg + 16_light_snap-intersection.svg + 22_light_snap-intersection.svg + 24_light_snap-intersection.svg + 16_dark_snap-node.svg + 22_dark_snap-node.svg + 24_dark_snap-node.svg + 16_light_snap-node.svg + 22_light_snap-node.svg + 24_light_snap-node.svg + 22_dark_snap-orthogonal.svg + 24_dark_snap-orthogonal.svg + 22_light_snap-orthogonal.svg + 24_light_snap-orthogonal.svg + 16_dark_split.svg + 22_dark_split.svg + 24_dark_split.svg + 16_light_split.svg + 22_light_split.svg + 24_light_split.svg + 16_dark_tab-close.svg + 22_dark_tab-close.svg + 24_dark_tab-close.svg + 16_light_tab-close.svg + 22_light_tab-close.svg + 24_light_tab-close.svg + 16_dark_tab-new.svg + 22_dark_tab-new.svg + 24_dark_tab-new.svg + 16_light_tab-new.svg + 22_light_tab-new.svg + 24_light_tab-new.svg + 16_dark_transform-move.svg + 22_dark_transform-move.svg + 24_dark_transform-move.svg + 16_light_transform-move.svg + 22_light_transform-move.svg + 24_light_transform-move.svg + 16_dark_trash-empty.svg + 22_dark_trash-empty.svg + 24_dark_trash-empty.svg + 16_light_trash-empty.svg + 22_light_trash-empty.svg + 24_light_trash-empty.svg + 16_dark_zoom-fit-best.svg + 22_dark_zoom-fit-best.svg + 24_dark_zoom-fit-best.svg + 16_light_zoom-fit-best.svg + 22_light_zoom-fit-best.svg + 24_light_zoom-fit-best.svg + 16_dark_warning.svg + 16_light_warning.svg + diff --git a/plugins/extensions/imagesize/wdg_canvassize.ui b/plugins/extensions/imagesize/wdg_canvassize.ui --- a/plugins/extensions/imagesize/wdg_canvassize.ui +++ b/plugins/extensions/imagesize/wdg_canvassize.ui @@ -6,8 +6,8 @@ 0 0 - 367 - 380 + 395 + 387 @@ -24,7 +24,7 @@ - + 1 @@ -47,10 +47,10 @@ - + - + 1 @@ -70,7 +70,7 @@ - + 4 @@ -86,7 +86,7 @@ - + 4 @@ -204,7 +204,7 @@ - + -100000 @@ -214,7 +214,7 @@ - + 4 @@ -266,7 +266,7 @@ - + -100000 @@ -276,7 +276,7 @@ - + 4 @@ -331,7 +331,7 @@ 2 - + @@ -652,6 +652,16 @@
kcanvaspreview.h
1 + + KisDoubleParseSpinBox + QDoubleSpinBox +
kis_doubleparsespinbox.h
+
+ + KisIntParseSpinBox + QSpinBox +
kis_intparsespinbox.h
+
newWidth