diff --git a/libs/widgets/kis_double_parse_unit_spin_box.cpp b/libs/widgets/kis_double_parse_unit_spin_box.cpp index d221f417c2..568d6b5a93 100644 --- a/libs/widgets/kis_double_parse_unit_spin_box.cpp +++ b/libs/widgets/kis_double_parse_unit_spin_box.cpp @@ -1,220 +1,270 @@ /* * 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_double_parse_unit_spin_box.h" #include "kis_spin_box_unit_manager.h" +#include class Q_DECL_HIDDEN KisDoubleParseUnitSpinBox::Private { public: Private(double low, double up, double step) : lowerInPoints(low), upperInPoints(up), stepInPoints(step), unit(KoUnit(KoUnit::Point)), unitManager() { } double lowerInPoints; ///< lowest value in points double upperInPoints; ///< highest value in points double stepInPoints; ///< step in points KoUnit unit; KisSpinBoxUnitManager unitManager; //manage more units than permitted by KoUnit. }; KisDoubleParseUnitSpinBox::KisDoubleParseUnitSpinBox(QWidget *parent) : KisDoubleParseSpinBox(parent), d(new Private(-9999, 9999, 1)) { setUnit( KoUnit(KoUnit::Point) ); setAlignment( Qt::AlignRight ); connect(this, SIGNAL(valueChanged( double )), SLOT(privateValueChanged())); + connect(lineEdit(), SIGNAL(textChanged(QString)), + this, SLOT(detectUnitChanges()) ); + + } KisDoubleParseUnitSpinBox::~KisDoubleParseUnitSpinBox() { delete d; } void KisDoubleParseUnitSpinBox::changeValue( double newValue ) { if (d->unitManager.getApparentValue(newValue) == KisDoubleParseSpinBox::value()) { return; } KisDoubleParseSpinBox::setValue( d->unitManager.getApparentValue(newValue) ); } void KisDoubleParseUnitSpinBox::setUnit( const KoUnit & unit) { if (d->unitManager.getUnitDimensionType() != KisSpinBoxUnitManager::LENGTH) { d->unitManager.setUnitDim(KisSpinBoxUnitManager::LENGTH); //setting the unit using a KoUnit mean you want to use a length. } setUnit(unit.symbol()); d->unit = unit; } void KisDoubleParseUnitSpinBox::setUnit(const QString &symbol) { double oldValue = d->unitManager.getReferenceValue(KisDoubleParseSpinBox::value()); QString oldSymbol = d->unitManager.getApparentUnitSymbol(); if (symbol == oldSymbol) { return; } d->unitManager.setApparentUnitFromSymbol(symbol); if (d->unitManager.getApparentUnitSymbol() == oldSymbol) { //the setApparentUnitFromSymbol is a bit clever, for example in regard of Casesensitivity. So better check like this. return; } KisDoubleParseSpinBox::setMinimum( d->unitManager.getApparentValue( d->lowerInPoints ) ); KisDoubleParseSpinBox::setMaximum( d->unitManager.getApparentValue( d->upperInPoints ) ); qreal step = d->unitManager.getApparentValue( d->stepInPoints ); if (symbol == KoUnit(KoUnit::Pixel).symbol()) { // limit the pixel step by 1.0 step = qMax(qreal(1.0), step); } KisDoubleParseSpinBox::setSingleStep( step ); KisDoubleParseSpinBox::setValue( d->unitManager.getApparentValue( oldValue ) ); } void KisDoubleParseUnitSpinBox::setDimensionType(int dim) { if (!KisSpinBoxUnitManager::isUnitId(dim)) { return; } d->unitManager.setUnitDim((KisSpinBoxUnitManager::UnitDimension) dim); } double KisDoubleParseUnitSpinBox::value( ) const { return d->unitManager.getReferenceValue( KisDoubleParseSpinBox::value() ); } void KisDoubleParseUnitSpinBox::setMinimum(double min) { d->lowerInPoints = min; KisDoubleParseSpinBox::setMinimum( d->unitManager.getApparentValue( min ) ); } void KisDoubleParseUnitSpinBox::setMaximum(double max) { d->upperInPoints = max; KisDoubleParseSpinBox::setMaximum( d->unitManager.getApparentValue( max ) ); } void KisDoubleParseUnitSpinBox::setLineStep(double step) { d->stepInPoints = d->unitManager.getReferenceValue(step); KisDoubleParseSpinBox::setSingleStep( step ); } void KisDoubleParseUnitSpinBox::setLineStepPt(double step) { d->stepInPoints = step; KisDoubleParseSpinBox::setSingleStep( d->unitManager.getApparentValue( step ) ); } void KisDoubleParseUnitSpinBox::setMinMaxStep( double min, double max, double step ) { setMinimum( min ); setMaximum( max ); setLineStepPt( step ); } QValidator::State KisDoubleParseUnitSpinBox::validate(QString &input, int &pos) const { Q_UNUSED(pos); QRegExp regexp ("([ a-zA-Z]+)$"); // Letters or spaces at end const int res = input.indexOf( regexp ); if ( res == -1 ) { // Nothing like an unit? The user is probably editing the unit return QValidator::Intermediate; } QString expr ( input.left( res ) ); const QString unitName ( regexp.cap( 1 ).trimmed().toLower() ); bool ok = true; bool interm = false; QValidator::State exprState = KisDoubleParseSpinBox::validate(expr, pos); if (exprState == QValidator::Invalid) { return exprState; } else if (exprState == QValidator::Intermediate) { interm = true; } //check if we can parse the unit. QStringList listOfSymbol = d->unitManager.getsUnitSymbolList(); ok = listOfSymbol.contains(unitName); if (!ok || interm) { return QValidator::Intermediate; } return QValidator::Acceptable; } QString KisDoubleParseUnitSpinBox::textFromValue( double value ) const { - return KisDoubleParseSpinBox::textFromValue(value) + " " + d->unitManager.getApparentUnitSymbol(); + QString txt = KisDoubleParseSpinBox::textFromValue(value); + if (!txt.endsWith(d->unitManager.getApparentUnitSymbol())) { + txt += " " + d->unitManager.getApparentUnitSymbol(); + } + return txt; } QString KisDoubleParseUnitSpinBox::veryCleanText() const { - QString expr = cleanText(); - QString symbol = d->unitManager.getApparentUnitSymbol(); - - expr = expr.trimmed(); - if ( expr.endsWith(symbol) ) { - expr.remove(expr.size()-symbol.size(), symbol.size()); - } - - return expr; + return makeTextClean(cleanText()); } double KisDoubleParseUnitSpinBox::valueFromText( const QString& str ) const { - return KisDoubleParseSpinBox::valueFromText(veryCleanText()); //this function will take care of prefix (and don't mind if suffix has been removed. + + QString txt = makeTextClean(str); + + return KisDoubleParseSpinBox::valueFromText(txt); //this function will take care of prefix (and don't mind if suffix has been removed. } void KisDoubleParseUnitSpinBox::privateValueChanged() { emit valueChangedPt( value() ); } + +QString KisDoubleParseUnitSpinBox::detectUnit() +{ + QString str = veryCleanText().trimmed(); //text with the new unit but not the old one. + + QRegExp regexp ("([ ]*[a-zA-Z]+[ ]*)$"); // Letters or spaces at end + int res = str.indexOf( regexp ); + + if (res > -1) { + QString expr ( str.right( str.size() - res ) ); + expr = expr.trimmed(); + return expr; + } + + return ""; +} + +void KisDoubleParseUnitSpinBox::detectUnitChanges() +{ + QString unitSymb = detectUnit(); + + if (unitSymb.isEmpty()) { + return; + } + + setUnit(unitSymb); + setValue(valueFromText(cleanText())); //change value keep the old value, but converted to new unit... which is different from the value the user entered in the new unit. So we need to set the new value. +} + +QString KisDoubleParseUnitSpinBox::makeTextClean(QString const& txt) const +{ + QString expr = txt; + QString symbol = d->unitManager.getApparentUnitSymbol(); + + if ( expr.endsWith(suffix()) ) { + expr.remove(expr.size()-suffix().size(), suffix().size()); + } + + expr = expr.trimmed(); + + if ( expr.endsWith(symbol) ) { + expr.remove(expr.size()-symbol.size(), symbol.size()); + } + + return expr; +} diff --git a/libs/widgets/kis_double_parse_unit_spin_box.h b/libs/widgets/kis_double_parse_unit_spin_box.h index e03a915737..9c6b8b3575 100644 --- a/libs/widgets/kis_double_parse_unit_spin_box.h +++ b/libs/widgets/kis_double_parse_unit_spin_box.h @@ -1,111 +1,117 @@ /* * 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_DOUBLEPARSEUNITSPINBOX_H #define KIS_DOUBLEPARSEUNITSPINBOX_H #include #include "kis_double_parse_spin_box.h" #include "kritawidgets_export.h" /*! * \brief The KisDoubleParseUnitSpinBox class is an evolution of the \see KoUnitDoubleSpinBox, but inherit from \see KisDoubleParseSpinBox to be able to parse math expressions. * * This class store the */ class KRITAWIDGETS_EXPORT KisDoubleParseUnitSpinBox : public KisDoubleParseSpinBox { Q_OBJECT public: KisDoubleParseUnitSpinBox(QWidget* parent = 0); ~KisDoubleParseUnitSpinBox(); /** * Set the new value in points which will then be converted to the current unit for display * @param newValue the new value * @see value() */ virtual void changeValue( double newValue ); + /** * This spinbox shows the internal value after a conversion to the unit set here. */ virtual void setUnit(const KoUnit &unit); - virtual void setUnit(const QString &symbol); + virtual void setUnit(const QString & symbol); /** * @brief setDimensionType set the dimension (for example length or angle) of the units the spinbox manage * @param dim the dimension id. (if not an id in KisSpinBoxUnitManager::UnitDimension, then the function does nothing). */ virtual void setDimensionType(int dim); /// @return the current value, converted in points double value( ) const; /// Set minimum value in points. void setMinimum(double min); /// Set maximum value in points. void setMaximum(double max); /// Set step size in the current unit. void setLineStep(double step); /// Set step size in points. void setLineStepPt(double step); /// Set minimum, maximum value and the step size (all in points) void setMinMaxStep( double min, double max, double step ); /// reimplemented from superclass, will forward to KoUnitDoubleValidator virtual QValidator::State validate(QString &input, int &pos) const; /** * Transform the double in a nice text, using locale symbols * @param value the number as double * @return the resulting string */ virtual QString textFromValue( double value ) const; //! \brief get the text in the spinbox without prefix or suffix, and remove unit symbol if present. virtual QString veryCleanText() const; /** * Transfrom a string into a double, while taking care of locale specific symbols. * @param str the string to transform into a number * @return the value as double */ virtual double valueFromText( const QString& str ) const; Q_SIGNALS: /// emitted like valueChanged in the parent, but this one emits the point value void valueChangedPt( qreal ); private: class Private; Private * const d; + QString detectUnit(); + QString makeTextClean(QString const& txt) const; + private Q_SLOTS: // exists to do emits for valueChangedPt void privateValueChanged(); + void detectUnitChanges(); + }; #endif // KIS_DOUBLEPARSEUNITSPINBOX_H diff --git a/libs/widgetutils/kis_spin_box_unit_manager.cpp b/libs/widgetutils/kis_spin_box_unit_manager.cpp index 3c49b938c3..8896771561 100644 --- a/libs/widgetutils/kis_spin_box_unit_manager.cpp +++ b/libs/widgetutils/kis_spin_box_unit_manager.cpp @@ -1,323 +1,326 @@ /* * 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_spin_box_unit_manager.h" #include "KoUnit.h" #include #include const QStringList KisSpinBoxUnitManager::referenceUnitSymbols = {"pt", "°", "frame"}; class Q_DECL_HIDDEN KisSpinBoxUnitManager::Private { public: Private(KisSpinBoxUnitManager::UnitDimension pDim = KisSpinBoxUnitManager::LENGTH, QString pUnitSymbol = "pt", double pConv = 1.0): dim(pDim), unitSymbol(pUnitSymbol), conversionFactor(pConv), constrains(0), unitListCached(false), hasHundredPercent(false), hasWidthAndHeight(false) { } KisSpinBoxUnitManager::UnitDimension dim; QString unitSymbol; double conversionFactor; KisSpinBoxUnitManager::Constrains constrains; mutable QStringList unitList; mutable bool unitListCached; mutable QStringList unitListWithName; mutable bool unitListWithNameCached; bool hasHundredPercent; qreal hundredPercent; bool hasWidthAndHeight; qreal width; qreal height; }; KisSpinBoxUnitManager::KisSpinBoxUnitManager(QObject *parent) : QObject(parent) { d = new Private(); } KisSpinBoxUnitManager::~KisSpinBoxUnitManager() { delete d; } int KisSpinBoxUnitManager::getUnitDimensionType() const { return d->dim; } QString KisSpinBoxUnitManager::getReferenceUnitSymbol() const { return referenceUnitSymbols[d->dim]; } QString KisSpinBoxUnitManager::getApparentUnitSymbol() const { return d->unitSymbol; } int KisSpinBoxUnitManager::getApparentUnitId() const { QStringList list = getsUnitSymbolList(); return list.indexOf(d->unitSymbol); } QStringList KisSpinBoxUnitManager::getsUnitSymbolList(bool withName) const{ QStringList list; //TODO: cache if (withName) { if (d->unitListWithNameCached) { return d->unitListWithName; } } else { if (d->unitListCached) { return d->unitList; } } switch (d->dim) { case LENGTH: for (int i = 0; i < KoUnit::TypeCount; i++) { if (withName) { list << KoUnit::unitDescription(KoUnit::Type(i)); } else { list << KoUnit(KoUnit::Type(i)).symbol(); } } break; case ANGLE: if (withName) { list << i18n("degrees (°)") << i18n("radians (rad)") << i18n("gons (gon)") << i18n("percent of circle (%)"); } else { list << "°" << "rad" << "gon" << "%"; } break; case TIME: if (withName) { list << i18n("frames (f)"); } else { list << "f"; } break; } if (withName) { d->unitListWithName = list; d->unitListWithNameCached = true; } else { d->unitList = list; d->unitListCached = true; } return list; } qreal KisSpinBoxUnitManager::getReferenceValue(double apparentValue) const { qreal v = apparentValue/d->conversionFactor; if (d->constrains &= REFISINT) { v = qFloor(v); } return v; } qreal KisSpinBoxUnitManager::getApparentValue(double refValue) const { qreal v = refValue*d->conversionFactor; if (d->constrains &= VALISINT) { v = qFloor(v); } return v; } qreal KisSpinBoxUnitManager::getConversionFactor(UnitDimension dim, QString symbol) const { qreal factor = -1; switch (dim) { case LENGTH: do { bool ok; KoUnit unit = KoUnit::fromSymbol(symbol, &ok); if (! ok) { break; } factor = unit.toUserValue(1.0); } while (0) ; break; case ANGLE: if (symbol == "°") { factor = 1.0; break; } if (symbol == "rad") { factor = acos(-1)/90.0; break; } if (symbol == "gon") { factor = 10.0/9.0; break; } if (symbol == "%") { factor = 2.5/9.0; //(25% of circle is 90°) break; } break; case TIME: if (symbol != "f") { //we have only frames for the moment. break; } factor = 1.0; break; } return factor; } void KisSpinBoxUnitManager::setUnitDim(UnitDimension dim) { if (dim == d->dim) { return; } d->dim = dim; d->unitSymbol = referenceUnitSymbols[d->dim]; //Active dim is reference dim when just changed. d->conversionFactor = 1.0; emit unitDimensionChanged(d->dim); } void KisSpinBoxUnitManager::setApparentUnitFromSymbol(QString pSymbol) { QString symbol = pSymbol.trimmed(); if (symbol == d->unitSymbol) { return; } QString newSymb = ""; switch (d->dim) { case ANGLE: if (symbol.toLower() == "deg") { newSymb = "°"; break; } goto default_indentifier; //alway do default after handling possible special cases. default_indentifier: default: QStringList list = getsUnitSymbolList(); if (list.contains(symbol, Qt::CaseInsensitive)) { for (QString str : list) { if (str.toLower() == symbol.toLower()) { newSymb = str; //official symbol may contain capitals letters, so better take the official version. break; } } break; } } if(newSymb.isEmpty()) { return; //abort if it was impossible to locate the correct symbol. } qreal conversFact = getConversionFactor(d->dim, newSymb); qreal oldConversFact = d->conversionFactor; d->conversionFactor = conversFact; emit conversionFactorChanged(d->conversionFactor, oldConversFact); + d->unitSymbol = newSymb; + emit unitChanged(newSymb); + } void KisSpinBoxUnitManager::configureRelativeUnitReference(qreal value) { if (d->hasHundredPercent && d->hundredPercent == value) { return; } if (!d->hasHundredPercent) { //in case we add relative units we need to clear cache for unitlists. d->unitListCached = false; d->unitListWithNameCached = false; emit unitListChanged(); } d->hundredPercent = value; d->hasHundredPercent = true; } void KisSpinBoxUnitManager::configureRelativeUnitWidthAndHeight(qreal width, qreal height) { if (d->hasWidthAndHeight && d->width == width && d->height == height) { return; } if (!d->hasWidthAndHeight) { //in case we add relative units we need to clear cache for unitlists. d->unitListCached = false; d->unitListWithNameCached = false; emit unitListChanged(); } d->width = width; d->height = height; d->hasWidthAndHeight = true; } diff --git a/libs/widgetutils/kis_spin_box_unit_manager.h b/libs/widgetutils/kis_spin_box_unit_manager.h index 1b7eab1727..f707db246e 100644 --- a/libs/widgetutils/kis_spin_box_unit_manager.h +++ b/libs/widgetutils/kis_spin_box_unit_manager.h @@ -1,102 +1,103 @@ /* * 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 KISSPINBOXUNITMANAGER_H #define KISSPINBOXUNITMANAGER_H #include #include #include "kritawidgetutils_export.h" /** * @brief The KisSpinBoxUnitManager class is an abstract interface for the unitspinboxes classes to manage different type of units. * * The class make a difference between unit dimension (distance, angle, time), unit reference mode (absolute or relative). * If one want to use relative units a reference must be configured using the proper function. * * The class allow to converte values between reference unit and apparent unit, but also to get other information like possible unit symbols. * */ class KRITAWIDGETUTILS_EXPORT KisSpinBoxUnitManager : public QObject { Q_OBJECT public: enum UnitDimension{ LENGTH = 0, ANGLE = 1, TIME = 2 }; static inline bool isUnitId(int code) { return (code == LENGTH || code == ANGLE || code == TIME); } //! \brief this list hold the symbols of the referenc unit per dimension. The index is equal to the value in UnitDimension so that the dimension name can be used to index the list. static const QStringList referenceUnitSymbols; enum Constrain{ NOCONSTR = 0, REFISINT = 1, VALISINT = 2 }; Q_DECLARE_FLAGS(Constrains, Constrain) explicit KisSpinBoxUnitManager(QObject *parent = 0); ~KisSpinBoxUnitManager(); int getUnitDimensionType() const; QString getReferenceUnitSymbol() const; QString getApparentUnitSymbol() const; //! \brief get the position of the apparent unit in the list of units. It is usefull if we want to build a model for combo-box based unit management. int getApparentUnitId() const; QStringList getsUnitSymbolList(bool withName = false) const; qreal getReferenceValue(double apparentValue) const; qreal getApparentValue(double refValue) const; qreal getConversionFactor(UnitDimension dim, QString symbol) const; Q_SIGNALS: void unitDimensionChanged(int dimCode); + void unitChanged(QString symbol); void conversionFactorChanged(qreal newConversionFactor, qreal oldConversionFactor); void unitListChanged(); public Q_SLOTS: void setUnitDim(UnitDimension dim); void setApparentUnitFromSymbol(QString pSymbol); //! \brief configure the reference length (100%) in reference unit. This activate relative units. void configureRelativeUnitReference(qreal value); void configureRelativeUnitWidthAndHeight(qreal width, qreal height); protected: class Private; Private * d; }; #endif // KISSPINBOXUNITMANAGER_H