Index: libs/global/CMakeLists.txt =================================================================== --- libs/global/CMakeLists.txt +++ libs/global/CMakeLists.txt @@ -13,6 +13,7 @@ kis_shared.cpp kis_dom_utils.cpp kis_painting_tweaks.cpp + kis_numparser.cpp ) add_library(kritaglobal SHARED ${kritaglobal_LIB_SRCS} ) Index: libs/global/kis_numparser.h =================================================================== --- /dev/null +++ libs/global/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 "kritaglobal_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. + KRITAGLOBAL_EXPORT double parseSimpleMathExpr(QString const& expr, bool* noProblem = 0); + + //! \brief parse an expression to an int. + KRITAGLOBAL_EXPORT int parseIntegerMathExpr(QString const& expr, bool* noProblem = 0); +} + +#endif // KIS_NUMPARSER_H + Index: libs/global/kis_numparser.cpp =================================================================== --- /dev/null +++ libs/global/kis_numparser.cpp @@ -0,0 +1,600 @@ +/* + * 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 +int treatLevel1Int(QString const& expr, bool & noProblem); +int treatLevel2Int(QString const& expr, bool & noProblem); +int 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 treatLevel1Int(expr, *noProblem); + } + + return 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. + */ +int 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; + } + + int 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. + */ +int 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; + } + + int 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. + */ +int 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]; + + int val = treatLevel1Int(subExpr, noProblem); + + if (!noProblem) { + return 0; + } + + return sign*val; + + } else if(integerExp.exactMatch(expr.trimmed())) { + return QVariant(expr).toInt(&noProblem); + } else if(numberExp.exactMatch(expr.trimmed())) { + double value = qFloor( QVariant(expr).toDouble(&noProblem)); + return (value > 0.0) ? value: value + 1; + } + + noProblem = false; + return 0; + +} Index: libs/ui/CMakeLists.txt =================================================================== --- libs/ui/CMakeLists.txt +++ 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 Index: libs/ui/widgets/kis_doubleparsespinbox.h =================================================================== --- /dev/null +++ libs/ui/widgets/kis_doubleparsespinbox.h @@ -0,0 +1,70 @@ +/* + * 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 + +/*! + * \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 usefull. + +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 usefull to let the widget change it's stylesheet when an error occured in the last expression. + void setErrorStyle(); + //! \brief usefull 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; +}; + +#endif // KISDOUBLEPARSESPINBOX_H Index: libs/ui/widgets/kis_doubleparsespinbox.cpp =================================================================== --- /dev/null +++ libs/ui/widgets/kis_doubleparsespinbox.cpp @@ -0,0 +1,140 @@ +/* + * 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" + +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(); + +} + +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 (!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;"); + } +} + +void KisDoubleParseSpinBox::clearErrorStyle() +{ + if (_isLastValid) { + setStyleSheet(""); + } +} +void KisDoubleParseSpinBox::clearError() +{ + _isLastValid = true; + emit noMoreParsingError(); + _oldValue = value(); + clearErrorStyle(); +} Index: libs/ui/widgets/kis_intparsespinbox.h =================================================================== --- /dev/null +++ libs/ui/widgets/kis_intparsespinbox.h @@ -0,0 +1,70 @@ +/* + * 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" + +/*! + * \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 usefull. + +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 usefull to let the widget change it's stylesheet when an error occured in the last expression. + void setErrorStyle(); + //! \brief usefull 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, to +}; + +#endif // KISINTPARSESPINBOX_H Index: libs/ui/widgets/kis_intparsespinbox.cpp =================================================================== --- /dev/null +++ libs/ui/widgets/kis_intparsespinbox.cpp @@ -0,0 +1,154 @@ +/* + * 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 + +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())); + +} + +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;"); + } +} + +void KisIntParseSpinBox::clearErrorStyle() +{ + if (_isLastValid) { + setStyleSheet(""); + } +} + + +void KisIntParseSpinBox::clearError() +{ + _isLastValid = true; + emit noMoreParsingError(); + _oldVal = value(); + clearErrorStyle(); +} Index: plugins/extensions/imagesize/wdg_canvassize.ui =================================================================== --- plugins/extensions/imagesize/wdg_canvassize.ui +++ 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