File indexing completed on 2024-05-12 16:02:27

0001 /*
0002  *  SPDX-FileCopyrightText: 2016 Laurent Valentin Jospin <laurent.valentin@famillejospin.ch>
0003  *  SPDX-FileCopyrightText: 2021 Deif Lou <ginoba@gmail.com>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "kis_double_parse_unit_spin_box.h"
0009 #include "kis_spin_box_unit_manager.h"
0010 
0011 #include <QLineEdit>
0012 
0013 class Q_DECL_HIDDEN KisDoubleParseUnitSpinBox::Private
0014 {
0015 public:
0016     Private(double low, double up, double step, KisSpinBoxUnitManager* unitManager)
0017         : lowerInPoints(low),
0018           upperInPoints(up),
0019           stepInPoints(step),
0020           unit(KoUnit(KoUnit::Point)),
0021           outPutSymbol(""),
0022           unitManager(unitManager),
0023           defaultUnitManager(unitManager)
0024     {
0025     }
0026 
0027     double lowerInPoints {0.0}; ///< lowest value in points
0028     double upperInPoints {0.0}; ///< highest value in points
0029     double stepInPoints {0.0};  ///< step in points
0030     KoUnit unit;
0031 
0032     double previousValueInPoint {0.0}; ///< allow to store the previous value in point, useful in some cases, even if, usually, we prefer to refer to the actual value (in selected unit) and convert it, since this is not always updated.
0033     QString previousSymbol;
0034     QString outPutSymbol;
0035 
0036     KisSpinBoxUnitManager* unitManager {0}; //manage more units than permitted by KoUnit.
0037     KisSpinBoxUnitManager* defaultUnitManager {0}; //the default unit manager is the one the spinbox rely on and go back to if a connected unit manager is destroyed before the spinbox.
0038 
0039     bool isDeleting {false};
0040 
0041     bool unitHasBeenChangedFromOutSideOnce {false}; //in some part of the code the unit is reset. We want to prevent this overriding the unit defined by the user. We use this switch to do so.
0042     bool letUnitBeChangedFromOutsideMoreThanOnce {true};
0043 
0044     bool displayUnit {true};
0045 
0046     bool allowResetDecimals {true};
0047 
0048     bool mustUsePreviousText {false};
0049 };
0050 
0051 KisDoubleParseUnitSpinBox::KisDoubleParseUnitSpinBox(QWidget *parent) :
0052     KisDoubleParseSpinBox(parent),
0053     d(new Private(-9999, 9999, 1, KisSpinBoxUnitManagerFactory::buildDefaultUnitManager(this)))
0054 {
0055     setUnit( KoUnit(KoUnit::Point) );
0056     setAlignment( Qt::AlignRight );
0057 
0058     connect(this, SIGNAL(valueChanged(double)), this, SLOT(privateValueChanged()));
0059     connect(lineEdit(), SIGNAL(textChanged(QString)),
0060             this, SLOT(detectUnitChanges()) );
0061 
0062     connect(d->unitManager, (void (KisSpinBoxUnitManager::*)()) &KisSpinBoxUnitManager::unitAboutToChange, this, (void (KisDoubleParseUnitSpinBox::*)()) &KisDoubleParseUnitSpinBox::prepareUnitChange);
0063     connect(d->unitManager, (void (KisSpinBoxUnitManager::*)( QString )) &KisSpinBoxUnitManager::unitChanged, this, (void (KisDoubleParseUnitSpinBox::*)( QString const& )) &KisDoubleParseUnitSpinBox::internalUnitChange);
0064 
0065     setDecimals(d->unitManager->getApparentUnitRecommandedDecimals());
0066 
0067 }
0068 
0069 KisDoubleParseUnitSpinBox::~KisDoubleParseUnitSpinBox()
0070 {
0071     d->isDeleting = true;
0072     delete d->defaultUnitManager;
0073     delete d;
0074 }
0075 
0076 void KisDoubleParseUnitSpinBox::setUnitManager(KisSpinBoxUnitManager* unitManager)
0077 {
0078     qreal oldVal = d->unitManager->getReferenceValue(KisDoubleParseSpinBox::value());
0079     QString oldSymbol = d->unitManager->getApparentUnitSymbol();
0080 
0081     qreal newVal = 0.0;
0082 
0083     double newMin;
0084     double newMax;
0085     double newStep;
0086 
0087     if (oldSymbol == unitManager->getApparentUnitSymbol() &&
0088             d->unitManager->getUnitDimensionType() == unitManager->getUnitDimensionType())
0089     {
0090         d->unitManager = unitManager; //set the new unitmanager anyway, since it may be a subclass, so change the behavior anyway.
0091         goto connect_signals;
0092     }
0093 
0094     if (d->unitManager->getUnitDimensionType() == unitManager->getUnitDimensionType()) {
0095         //dimension is the same, calculate the new value
0096         newVal = unitManager->getApparentValue(oldVal);
0097     } else {
0098         newVal = unitManager->getApparentValue(d->lowerInPoints);
0099     }
0100 
0101     newMin = unitManager->getApparentValue(d->lowerInPoints);
0102     newMax = unitManager->getApparentValue(d->upperInPoints);
0103     newStep = unitManager->getApparentValue(d->stepInPoints);
0104 
0105     if (unitManager->getApparentUnitSymbol() == KoUnit(KoUnit::Pixel).symbol()) {
0106         // limit the pixel step by 1.0
0107         newStep = qMax(qreal(1.0), newStep);
0108     }
0109 
0110     KisDoubleParseSpinBox::setMinimum(newMin);
0111     KisDoubleParseSpinBox::setMaximum(newMax);
0112     KisDoubleParseSpinBox::setSingleStep(newStep);
0113 
0114 connect_signals:
0115 
0116     if (d->unitManager != d->defaultUnitManager) {
0117         disconnect(d->unitManager, &QObject::destroyed,
0118                    this, &KisDoubleParseUnitSpinBox::disconnectExternalUnitManager); //there's no dependence anymore.
0119     }
0120     disconnect(d->unitManager, (void (KisSpinBoxUnitManager::*)()) &KisSpinBoxUnitManager::unitAboutToChange, this, (void (KisDoubleParseUnitSpinBox::*)()) &KisDoubleParseUnitSpinBox::prepareUnitChange);
0121     disconnect(d->unitManager, (void (KisSpinBoxUnitManager::*)( QString )) &KisSpinBoxUnitManager::unitChanged, this, (void (KisDoubleParseUnitSpinBox::*)( QString const& )) &KisDoubleParseUnitSpinBox::internalUnitChange);
0122 
0123     d->unitManager = unitManager;
0124 
0125     connect(d->unitManager, &QObject::destroyed,
0126             this, &KisDoubleParseUnitSpinBox::disconnectExternalUnitManager);
0127 
0128 
0129     connect(d->unitManager, (void (KisSpinBoxUnitManager::*)()) &KisSpinBoxUnitManager::unitAboutToChange, this, (void (KisDoubleParseUnitSpinBox::*)()) &KisDoubleParseUnitSpinBox::prepareUnitChange);
0130     connect(d->unitManager, (void (KisSpinBoxUnitManager::*)( QString )) &KisSpinBoxUnitManager::unitChanged, this, (void (KisDoubleParseUnitSpinBox::*)( QString const& )) &KisDoubleParseUnitSpinBox::internalUnitChange);
0131 
0132     KisDoubleParseSpinBox::setValue(newVal);
0133 
0134     if (d->allowResetDecimals) { //if the user has not fixed the number of decimals.
0135         setDecimals(d->unitManager->getApparentUnitRecommandedDecimals());
0136     }
0137 }
0138 
0139 void KisDoubleParseUnitSpinBox::changeValue( double newValue )
0140 {
0141     double apparentValue;
0142     double fact = 0.0;
0143     double cons = 0.0;
0144 
0145     if (d->outPutSymbol.isEmpty()) {
0146         apparentValue = d->unitManager->getApparentValue(newValue);
0147     } else {
0148 
0149         fact = d->unitManager->getConversionFactor(d->unitManager->getUnitDimensionType(), d->outPutSymbol);
0150         cons = d->unitManager->getConversionConstant(d->unitManager->getUnitDimensionType(), d->outPutSymbol);
0151 
0152         apparentValue = fact*newValue + cons;
0153     }
0154 
0155     if (apparentValue == KisDoubleParseSpinBox::value()) {
0156         return;
0157     }
0158 
0159     if (d->outPutSymbol.isEmpty()) {
0160         KisDoubleParseSpinBox::setValue( d->unitManager->getApparentValue(newValue) );
0161     } else {
0162 
0163         KisDoubleParseSpinBox::setValue( d->unitManager->getApparentValue((newValue - cons)/fact) );
0164     }
0165 }
0166 
0167 void KisDoubleParseUnitSpinBox::setUnit( const KoUnit & unit)
0168 {
0169     if (d->unitHasBeenChangedFromOutSideOnce && !d->letUnitBeChangedFromOutsideMoreThanOnce) {
0170         return;
0171     }
0172 
0173     if (d->unitManager->getUnitDimensionType() != KisSpinBoxUnitManager::LENGTH) {
0174         d->unitManager->setUnitDimension(KisSpinBoxUnitManager::LENGTH); //setting the unit using a KoUnit mean you want to use a length.
0175     }
0176 
0177     setUnit(unit.symbol());
0178     d->unit = unit;
0179 }
0180 
0181 void KisDoubleParseUnitSpinBox::setUnit(const QString &symbol)
0182 {
0183     d->unitManager->setApparentUnitFromSymbol(symbol); //via signals and slots, the correct functions should be called.
0184 }
0185 
0186 void KisDoubleParseUnitSpinBox::setReturnUnit(const QString & symbol)
0187 {
0188     d->outPutSymbol = symbol;
0189 }
0190 
0191 void KisDoubleParseUnitSpinBox::prepareUnitChange() {
0192 
0193     d->previousValueInPoint = d->unitManager->getReferenceValue(KisDoubleParseSpinBox::value());
0194     d->previousSymbol = d->unitManager->getApparentUnitSymbol();
0195 }
0196 
0197 void KisDoubleParseUnitSpinBox::internalUnitChange(const QString &symbol) {
0198 
0199     //d->unitManager->setApparentUnitFromSymbol(symbol);
0200 
0201     if (d->unitManager->getApparentUnitSymbol() == d->previousSymbol) { //the setApparentUnitFromSymbol is a bit clever, for example in regard of Casesensitivity. So better check like this.
0202         return;
0203     }
0204 
0205     KisDoubleParseSpinBox::setMinimum( d->unitManager->getApparentValue( d->lowerInPoints ) );
0206     KisDoubleParseSpinBox::setMaximum( d->unitManager->getApparentValue( d->upperInPoints ) );
0207 
0208     qreal step = d->unitManager->getApparentValue( d->stepInPoints );
0209 
0210     if (symbol == KoUnit(KoUnit::Pixel).symbol()) {
0211         // limit the pixel step by 1.0
0212         step = qMax(qreal(1.0), step);
0213     }
0214 
0215     KisDoubleParseSpinBox::setSingleStep( step );
0216     KisDoubleParseSpinBox::setValue( d->unitManager->getApparentValue( d->previousValueInPoint ) );
0217 
0218     if (d->allowResetDecimals) {
0219         setDecimals(d->unitManager->getApparentUnitRecommandedDecimals());
0220     }
0221 
0222     d->unitHasBeenChangedFromOutSideOnce = true;
0223 }
0224 
0225 void KisDoubleParseUnitSpinBox::setDimensionType(int dim)
0226 {
0227     if (!KisSpinBoxUnitManager::isUnitId(dim)) {
0228         return;
0229     }
0230 
0231     d->unitManager->setUnitDimension((KisSpinBoxUnitManager::UnitDimension) dim);
0232 }
0233 
0234 double KisDoubleParseUnitSpinBox::value( ) const
0235 {
0236     if (d->outPutSymbol.isEmpty()) {
0237         return d->unitManager->getReferenceValue( KisDoubleParseSpinBox::value() );
0238     }
0239 
0240     double ref = d->unitManager->getReferenceValue( KisDoubleParseSpinBox::value() );
0241     double fact = d->unitManager->getConversionFactor(d->unitManager->getUnitDimensionType(), d->outPutSymbol);
0242     double cons = d->unitManager->getConversionConstant(d->unitManager->getUnitDimensionType(), d->outPutSymbol);
0243 
0244     return fact*ref + cons;
0245 }
0246 
0247 void KisDoubleParseUnitSpinBox::setMinimum(double min)
0248 {
0249     d->lowerInPoints = min;
0250     KisDoubleParseSpinBox::setMinimum( d->unitManager->getApparentValue( min ) );
0251 }
0252 
0253 void KisDoubleParseUnitSpinBox::setMaximum(double max)
0254 {
0255     d->upperInPoints = max;
0256     KisDoubleParseSpinBox::setMaximum( d->unitManager->getApparentValue( max ) );
0257 }
0258 
0259 void KisDoubleParseUnitSpinBox::setLineStep(double step)
0260 {
0261     d->stepInPoints = d->unitManager->getReferenceValue(step);
0262     KisDoubleParseSpinBox::setSingleStep( step );
0263 }
0264 
0265 void KisDoubleParseUnitSpinBox::setLineStepPt(double step)
0266 {
0267     d->stepInPoints = step;
0268     KisDoubleParseSpinBox::setSingleStep( d->unitManager->getApparentValue( step ) );
0269 }
0270 
0271 void KisDoubleParseUnitSpinBox::setMinMaxStep( double min, double max, double step )
0272 {
0273     setMinimum( min );
0274     setMaximum( max );
0275     setLineStepPt( step );
0276 }
0277 
0278 QString KisDoubleParseUnitSpinBox::textFromValue( double value ) const
0279 {
0280     // Just return the current value (for example when the user is editing)
0281     if (d->mustUsePreviousText) {
0282         return cleanText();
0283     }
0284     // Construct a new value
0285     QString txt = KisDoubleParseSpinBox::textFromValue(value);
0286     if (d->displayUnit) {
0287         if (!txt.endsWith(d->unitManager->getApparentUnitSymbol())) {
0288             txt += " " + d->unitManager->getApparentUnitSymbol();
0289         }
0290     }
0291     return txt;
0292 }
0293 
0294 QString KisDoubleParseUnitSpinBox::veryCleanText() const
0295 {
0296     return makeTextClean(cleanText());
0297 }
0298 
0299 double KisDoubleParseUnitSpinBox::valueFromText( const QString& str ) const
0300 {
0301     QString txt = makeTextClean(str);
0302     //this function will take care of prefix (and don't mind if suffix has been removed.
0303     return KisDoubleParseSpinBox::valueFromText(txt);
0304 }
0305 
0306 void KisDoubleParseUnitSpinBox::setUnitChangeFromOutsideBehavior(bool toggle)
0307 {
0308     d->letUnitBeChangedFromOutsideMoreThanOnce = toggle;
0309 }
0310 
0311 void KisDoubleParseUnitSpinBox::setDisplayUnit(bool toggle)
0312 {
0313     d->displayUnit = toggle;
0314 }
0315 
0316 void KisDoubleParseUnitSpinBox::preventDecimalsChangeFromUnitManager(bool prevent)
0317 {
0318     d->allowResetDecimals = !prevent;
0319 }
0320 
0321 void KisDoubleParseUnitSpinBox::privateValueChanged()
0322 {
0323     emit valueChangedPt( value() );
0324 }
0325 
0326 QString KisDoubleParseUnitSpinBox::detectUnit()
0327 {
0328     QString str = veryCleanText().trimmed(); //text with the new unit but not the old one.
0329 
0330     QRegExp regexp ("([ ]*[a-zA-Z]+[ ]*)$"); // Letters or spaces at end
0331     int res = str.indexOf( regexp );
0332 
0333     if (res > -1) {
0334         QString expr ( str.right( str.size() - res ) );
0335         expr = expr.trimmed();
0336         return expr;
0337     }
0338 
0339     return "";
0340 }
0341 
0342 void KisDoubleParseUnitSpinBox::detectUnitChanges()
0343 {
0344     QString unitSymb = detectUnit();
0345 
0346     if (unitSymb.isEmpty()) {
0347         return;
0348     }
0349 
0350     QString oldUnitSymb = d->unitManager->getApparentUnitSymbol();
0351 
0352     setUnit(unitSymb);
0353     // Quick hack
0354     // This function is called when the user changed the text and the call to
0355     // setValue will provoke a call to textFromValue which will return a new
0356     // text different from the current one. Since the following setValue is
0357     // called because of a user change, we use a flag to prevent the text from
0358     // changing
0359     d->mustUsePreviousText = true;
0360     // Change value keep the old value, but converted to new unit... which is
0361     // different from the value the user entered in the new unit. So we need
0362     // to set the new value.
0363     setValue(valueFromText(cleanText()));
0364     d->mustUsePreviousText = false;
0365 
0366     if (oldUnitSymb != d->unitManager->getApparentUnitSymbol()) {
0367         // the user has changed the unit, so we block changes from outside.
0368         setUnitChangeFromOutsideBehavior(false);
0369     }
0370 }
0371 
0372 QString KisDoubleParseUnitSpinBox::makeTextClean(QString const& txt) const
0373 {
0374     QString expr = txt;
0375     QString symbol = d->unitManager->getApparentUnitSymbol();
0376 
0377     if ( expr.endsWith(suffix()) ) {
0378         expr.remove(expr.size()-suffix().size(), suffix().size());
0379     }
0380 
0381     expr = expr.trimmed();
0382 
0383     if ( expr.endsWith(symbol) ) {
0384         expr.remove(expr.size()-symbol.size(), symbol.size());
0385     }
0386 
0387     return expr.trimmed();
0388 }
0389 
0390 void KisDoubleParseUnitSpinBox::disconnectExternalUnitManager()
0391 {
0392     if (!d->isDeleting)
0393     {
0394         setUnitManager(d->defaultUnitManager); //go back to default unit manager.
0395     }
0396 }