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

0001 /*
0002  *  SPDX-FileCopyrightText: 2017 Laurent Valentin Jospin <laurent.valentin@famillejospin.ch>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_spin_box_unit_manager.h"
0008 
0009 #include "KoUnit.h"
0010 #include <klocalizedstring.h>
0011 
0012 #include <QtMath>
0013 
0014 static QString percentStr()
0015 {
0016     static QString str = i18n("Percent (%)");
0017     return str;
0018 }
0019 
0020 KisSpinBoxUnitManagerBuilder* KisSpinBoxUnitManagerFactory::builder = nullptr;
0021 
0022 KisSpinBoxUnitManager* KisSpinBoxUnitManagerFactory::buildDefaultUnitManager(QObject* parent)
0023 {
0024     if (builder == nullptr) {
0025         return new KisSpinBoxUnitManager(parent);
0026     }
0027 
0028     return builder->buildUnitManager(parent);
0029 }
0030 
0031 void KisSpinBoxUnitManagerFactory::setDefaultUnitManagerBuilder(KisSpinBoxUnitManagerBuilder* pBuilder)
0032 {
0033     if (builder != nullptr) {
0034         delete builder; //The factory took over the lifecycle of the builder, so it delete it when replaced.
0035     }
0036 
0037     builder = pBuilder;
0038 }
0039 
0040 void KisSpinBoxUnitManagerFactory::clearUnitManagerBuilder()
0041 {
0042     if (builder != nullptr) {
0043         delete builder; //The factory took over the lifecycle of the builder, so it delete it when replaced.
0044     }
0045 
0046     builder = nullptr;
0047 }
0048 
0049 const QStringList KisSpinBoxUnitManager::referenceUnitSymbols = {"pt", "px", "°", "frame"};
0050 
0051 const QStringList KisSpinBoxUnitManager::documentRelativeLengthUnitSymbols = {"px", "vw", "vh"}; //px are relative to the resolution, vw and vh to the width and height.
0052 const QStringList KisSpinBoxUnitManager::documentRelativeTimeUnitSymbols = {"s", "%"}; //secondes are relative to the framerate, % to the sequence length.
0053 
0054 class Q_DECL_HIDDEN KisSpinBoxUnitManager::Private
0055 {
0056 public:
0057     Private(KisSpinBoxUnitManager::UnitDimension pDim = KisSpinBoxUnitManager::LENGTH,
0058             QString pUnitSymbol = "pt",
0059             double pConv = 1.0):
0060         dim(pDim),
0061         unitSymbol(pUnitSymbol),
0062         conversionFactor(pConv)
0063     {
0064 
0065     }
0066 
0067     KisSpinBoxUnitManager::UnitDimension dim;
0068 
0069     QString unitSymbol;
0070     mutable double conversionFactor;
0071     bool conversionFactorIsFixed {true}; //tell if it's possible to trust the conversion factor stored or if it's needed to recompute it.
0072     mutable double conversionConstant {0};
0073     bool conversionConstantIsFixed {true}; //tell if it's possible to trust the conversion constant stored or if it's needed to recompute it.
0074 
0075     KisSpinBoxUnitManager::Constrains constrains { KisSpinBoxUnitManager::NOCONSTR };
0076 
0077     mutable QStringList unitList;
0078     mutable bool unitListCached {false};
0079 
0080     mutable QStringList unitListWithName;
0081     mutable bool unitListWithNameCached {false};
0082 
0083     //it's possible to store a reference for the % unit, for length.
0084     bool hasHundredPercent {false};
0085     qreal hundredPercent {0};
0086 
0087     bool canAccessDocument {false};
0088 
0089     QVector<KisSpinBoxUnitManager*> connectedUnitManagers;
0090 };
0091 
0092 KisSpinBoxUnitManager::KisSpinBoxUnitManager(QObject *parent) : QAbstractListModel(parent)
0093 {
0094     d = new Private();
0095 
0096     connect(this, (void (KisSpinBoxUnitManager::*)( QString )) &KisSpinBoxUnitManager::unitChanged, this, &KisSpinBoxUnitManager::newUnitSymbolToUnitIndex);
0097 }
0098 KisSpinBoxUnitManager::~KisSpinBoxUnitManager()
0099 {
0100     delete d;
0101 }
0102 
0103 int KisSpinBoxUnitManager::getUnitDimensionType() const
0104 {
0105     return d->dim;
0106 }
0107 
0108 QString KisSpinBoxUnitManager::getReferenceUnitSymbol() const
0109 {
0110     return referenceUnitSymbols[d->dim];
0111 }
0112 
0113 QString KisSpinBoxUnitManager::getApparentUnitSymbol() const
0114 {
0115     return d->unitSymbol;
0116 }
0117 
0118 int KisSpinBoxUnitManager::getApparentUnitId() const
0119 {
0120     QStringList list = getsUnitSymbolList();
0121     return list.indexOf(d->unitSymbol);
0122 }
0123 
0124 int KisSpinBoxUnitManager::getApparentUnitRecommandedDecimals() const {
0125 
0126     switch (d->dim) {
0127 
0128     case LENGTH:
0129         if (d->unitSymbol == "px") {
0130             return 0;
0131         } else {
0132             return 2;
0133         }
0134     case IMLENGTH:
0135         if (d->unitSymbol == "px") {
0136             return 0;
0137         } else {
0138             return 2;
0139         }
0140     default: //by default return 2.
0141         break;
0142     }
0143 
0144     return 2;
0145 
0146 }
0147 
0148 QStringList KisSpinBoxUnitManager::getsUnitSymbolList(bool withName) const{
0149 
0150     QStringList list;
0151 
0152     if (withName) {
0153         if (d->unitListWithNameCached) {
0154             return d->unitListWithName;
0155         }
0156     } else {
0157         if (d->unitListCached) {
0158             return d->unitList;
0159         }
0160     }
0161 
0162     switch (d->dim) {
0163 
0164     case LENGTH:
0165 
0166         for (int i = 0; i < KoUnit::TypeCount; i++) {
0167 
0168             if (KoUnit::Type(i) == KoUnit::Pixel) {
0169                 continue; //skip pixel, which is a document relative unit, in the base class.
0170             }
0171 
0172             if (withName) {
0173                 list << KoUnit::unitDescription(KoUnit::Type(i));
0174             } else {
0175                 list << KoUnit(KoUnit::Type(i)).symbol();
0176             }
0177         }
0178 
0179         if (hasPercent(LENGTH)) {
0180 
0181             if (withName) {
0182                 list << percentStr();
0183             } else {
0184                 list << "%";
0185             }
0186 
0187         }
0188 
0189         if (d->canAccessDocument) {
0190             // ad document relative units
0191             if (withName) {
0192                 list << KoUnit::unitDescription(KoUnit::Pixel) << i18n("percent of view width (vw)") << i18n("percent of view height (vh)");
0193             } else {
0194                 list << documentRelativeLengthUnitSymbols;
0195             }
0196         }
0197 
0198         break;
0199 
0200     case IMLENGTH:
0201 
0202         if (withName) {
0203             list << KoUnit::unitDescription(KoUnit::Pixel);
0204         } else {
0205             list << "px";
0206         }
0207 
0208         if (hasPercent(IMLENGTH)) {
0209 
0210             if (withName) {
0211                 list << percentStr();
0212             } else {
0213                 list << "%";
0214             }
0215 
0216         }
0217 
0218         if (d->canAccessDocument) {
0219             // ad document relative units
0220             if (withName) {
0221                 list << i18n("percent of view width (vw)") << i18n("percent of view height (vh)");
0222             } else {
0223                 list << "vw" << "vh";
0224             }
0225         }
0226         break;
0227 
0228     case ANGLE:
0229 
0230         if (withName) {
0231             list << i18n("degrees (°)") << i18n("radians (rad)") << i18n("gons (gon)") << i18n("percent of circle (%)");
0232         } else {
0233             list << "°" << "rad" << "gon" << "%";
0234         }
0235         break;
0236 
0237     case TIME:
0238 
0239         if (withName) {
0240             list << i18n("frames (f)");
0241         } else {
0242             list << "f";
0243         }
0244 
0245         if (d->canAccessDocument) {
0246             if (withName) {
0247                 list << i18n("seconds (s)") << i18n("percent of animation (%)");
0248             } else {
0249                 list << documentRelativeTimeUnitSymbols;
0250             }
0251         }
0252 
0253         break;
0254 
0255     }
0256 
0257     if (withName) {
0258         d->unitListWithName = list;
0259         d->unitListWithNameCached = true;
0260     } else {
0261         d->unitList = list;
0262         d->unitListCached = true;
0263     }
0264 
0265     return list;
0266 
0267 }
0268 
0269 qreal KisSpinBoxUnitManager::getConversionConstant(int dim, QString symbol) const
0270 {
0271     Q_UNUSED(dim);
0272     Q_UNUSED(symbol);
0273 
0274     return 0; // all units managed here are transform via a linear function, so this will always be 0 in this class.
0275 }
0276 
0277 qreal KisSpinBoxUnitManager::getReferenceValue(double apparentValue) const
0278 {
0279     if (!d->conversionFactorIsFixed) {
0280         recomputeConversionFactor();
0281     }
0282 
0283     if(!d->conversionConstantIsFixed) {
0284         recomputeConvesrionConstant();
0285     }
0286 
0287     qreal v = (apparentValue - d->conversionConstant)/d->conversionFactor;
0288 
0289     if (d->constrains &= REFISINT) {
0290         v = qFloor(v);
0291     }
0292 
0293     return v;
0294 
0295 }
0296 
0297 int KisSpinBoxUnitManager::rowCount(const QModelIndex &parent) const {
0298     if (parent == QModelIndex()) {
0299         return getsUnitSymbolList().size();
0300     }
0301     return 0;
0302 }
0303 
0304 QVariant KisSpinBoxUnitManager::data(const QModelIndex &index, int role) const {
0305 
0306     if (role == Qt::DisplayRole) {
0307 
0308         return getsUnitSymbolList(false).at(index.row());
0309 
0310     } else if (role == Qt::ToolTipRole) {
0311 
0312         return getsUnitSymbolList(true).at(index.row());
0313 
0314     }
0315 
0316     return QVariant();
0317 }
0318 
0319 qreal KisSpinBoxUnitManager::getApparentValue(double refValue) const
0320 {
0321     if (!d->conversionFactorIsFixed) {
0322         recomputeConversionFactor();
0323     }
0324 
0325     if(!d->conversionConstantIsFixed) {
0326         recomputeConvesrionConstant();
0327     }
0328 
0329     qreal v = refValue*d->conversionFactor + d->conversionConstant;
0330 
0331     if (d->constrains &= VALISINT) {
0332         v = qFloor(v);
0333     }
0334 
0335     return v;
0336 }
0337 
0338 qreal KisSpinBoxUnitManager::getConversionFactor(int dim, QString symbol) const
0339 {
0340 
0341     qreal factor = -1;
0342 
0343     switch (dim) {
0344 
0345     case LENGTH:
0346         do {
0347             if (symbol == "px") {
0348                 break;
0349             }
0350 
0351             bool ok;
0352             KoUnit unit = KoUnit::fromSymbol(symbol, &ok);
0353             if (! ok) {
0354                 break;
0355             }
0356             factor = unit.toUserValuePrecise(1.0); // use the precise function
0357         } while (0) ;
0358         break;
0359 
0360     case IMLENGTH:
0361         if (symbol == "px") {
0362             factor = 1;
0363         }
0364         break;
0365 
0366     case ANGLE:
0367         if (symbol == "°") {
0368             factor = 1.0;
0369             break;
0370         }
0371         if (symbol == "rad") {
0372             factor = acos(-1)/90.0;
0373             break;
0374         }
0375         if (symbol == "gon") {
0376             factor = 10.0/9.0;
0377             break;
0378         }
0379         if (symbol == "%") {
0380             factor = 2.5/9.0; //(25% of circle is 90°)
0381             break;
0382         }
0383         break;
0384 
0385     case TIME:
0386 
0387         if (symbol != "f") { //we have only frames for the moment.
0388             break;
0389         }
0390         factor = 1.0;
0391         break;
0392 
0393     default:
0394         break;
0395     }
0396 
0397     return factor;
0398 }
0399 
0400 
0401 void KisSpinBoxUnitManager::setUnitDimension(UnitDimension dimension)
0402 {
0403     if (dimension == d->dim) {
0404         return;
0405     }
0406 
0407     d->dim = dimension;
0408     d->unitSymbol = referenceUnitSymbols[d->dim]; //Active dim is reference dim when just changed.
0409     d->conversionFactor = 1.0;
0410 
0411     emit unitDimensionChanged(d->dim);
0412 
0413 }
0414 
0415 void KisSpinBoxUnitManager::setApparentUnitFromSymbol(QString pSymbol)
0416 {
0417 
0418     QString symbol = pSymbol.trimmed();
0419 
0420     if (symbol == d->unitSymbol) {
0421         return;
0422     }
0423 
0424     emit unitAboutToChange();
0425 
0426     QString newSymb = "";
0427 
0428     switch (d->dim) {
0429 
0430     case ANGLE:
0431         if (symbol.toLower() == "deg") {
0432             newSymb = "°";
0433             break;
0434         }
0435         goto default_indentifier; //always do default after handling possible special cases.
0436 
0437 default_indentifier:
0438     default:
0439         QStringList list = getsUnitSymbolList();
0440         if (list.contains(symbol, Qt::CaseInsensitive)) {
0441             for (QString str : list) {
0442                 if (str.toLower() == symbol.toLower()) {
0443                     newSymb = str; //official symbol may contain capitals letters, so better take the official version.
0444                     break;
0445                 }
0446             }
0447             break;
0448         }
0449 
0450     }
0451 
0452     if(newSymb.isEmpty()) {
0453         return; //abort if it was impossible to locate the correct symbol.
0454     }
0455 
0456     if (d->canAccessDocument) {
0457         //manage document relative units.
0458 
0459         QStringList speUnits;
0460 
0461         switch (d->dim) {
0462 
0463         case LENGTH:
0464             speUnits = documentRelativeLengthUnitSymbols;
0465             goto default_identifier_conv_fact;
0466 
0467         case IMLENGTH:
0468             speUnits << "vw" << "vh";
0469             goto default_identifier_conv_fact;
0470 
0471         case TIME:
0472             speUnits = documentRelativeTimeUnitSymbols;
0473             goto default_identifier_conv_fact;
0474 
0475 default_identifier_conv_fact:
0476         default:
0477 
0478             if (speUnits.isEmpty()) {
0479                 d->conversionFactorIsFixed = true;
0480                 break;
0481             }
0482 
0483             if (speUnits.contains(newSymb)) {
0484                 d->conversionFactorIsFixed = false;
0485                 break;
0486             }
0487 
0488             d->conversionFactorIsFixed = true;
0489             break;
0490         }
0491 
0492         if (d->dim == TIME) {
0493             if (newSymb == "%") {
0494                 d->conversionConstantIsFixed = false;
0495             }
0496         } else {
0497             d->conversionConstantIsFixed = true;
0498         }
0499 
0500     }
0501 
0502     qreal conversFact = getConversionFactor(d->dim, newSymb);
0503     qreal oldConversFact = d->conversionFactor;
0504 
0505     d->conversionFactor = conversFact;
0506     emit conversionFactorChanged(d->conversionFactor, oldConversFact);
0507 
0508     d->unitSymbol = newSymb;
0509     emit unitChanged(newSymb);
0510 
0511 }
0512 
0513 void KisSpinBoxUnitManager::selectApparentUnitFromIndex(int index) {
0514 
0515     if (index >= 0 && index < rowCount()) {
0516         setApparentUnitFromSymbol(getsUnitSymbolList().at(index));
0517     }
0518 
0519 }
0520 
0521 
0522 void KisSpinBoxUnitManager::syncWithOtherUnitManager(KisSpinBoxUnitManager* other) {
0523 
0524     if (d->connectedUnitManagers.indexOf(other) >= 0) {
0525         return;
0526     }
0527 
0528     if (other->getUnitDimensionType() == getUnitDimensionType()) { //sync only unitmanager of the same type.
0529         if (other->getsUnitSymbolList() == getsUnitSymbolList()) { //and if we have identical units available.
0530 
0531             connect(this, SIGNAL(unitChanged(int)), other, SLOT(selectApparentUnitFromIndex(int))); //sync units.
0532             connect(other, SIGNAL(unitChanged(int)), this, SLOT(selectApparentUnitFromIndex(int))); //sync units.
0533 
0534             d->connectedUnitManagers.append(other);
0535 
0536         }
0537     }
0538 
0539 }
0540 
0541 void KisSpinBoxUnitManager::clearSyncWithOtherUnitManager(KisSpinBoxUnitManager* other) {
0542 
0543     int id = d->connectedUnitManagers.indexOf(other);
0544 
0545     if (id < 0) {
0546         return;
0547     }
0548 
0549     disconnect(this, SIGNAL(unitChanged(int)), other, SLOT(selectApparentUnitFromIndex(int))); //unsync units.
0550     disconnect(other, SIGNAL(unitChanged(int)), this, SLOT(selectApparentUnitFromIndex(int))); //unsync units.
0551 
0552     d->connectedUnitManagers.removeAt(id);
0553 
0554 }
0555 
0556 void KisSpinBoxUnitManager::newUnitSymbolToUnitIndex(QString symbol) {
0557     int id = getsUnitSymbolList().indexOf(symbol);
0558 
0559     if (id >= 0) {
0560         emit unitChanged(id);
0561     }
0562 }
0563 
0564 bool KisSpinBoxUnitManager::hasPercent(int unitDim) const {
0565 
0566     if (unitDim == IMLENGTH || unitDim == LENGTH) {
0567         return false;
0568     }
0569 
0570     if (unitDim == TIME) {
0571         return d->canAccessDocument;
0572     }
0573 
0574     if (unitDim == ANGLE) {
0575         return true; //percent is fixed when considering angles.
0576     }
0577 
0578     return false;
0579 }
0580 
0581 void KisSpinBoxUnitManager::recomputeConversionFactor() const
0582 {
0583     if (d->conversionFactorIsFixed) {
0584         return;
0585     }
0586 
0587     qreal oldConversionFactor = d->conversionFactor;
0588 
0589     d->conversionFactor = getConversionFactor(d->dim, d->unitSymbol);
0590 
0591     if (oldConversionFactor != d->conversionFactor) {
0592         emit conversionFactorChanged(d->conversionFactor, oldConversionFactor);
0593     }
0594 }
0595 
0596 void KisSpinBoxUnitManager::recomputeConvesrionConstant() const
0597 {
0598     if (d->conversionConstantIsFixed) {
0599         return;
0600     }
0601 
0602     qreal oldConversionConstant = d->conversionConstant;
0603 
0604     d->conversionConstant = getConversionConstant(d->dim, d->unitSymbol);
0605 
0606     if (oldConversionConstant != d->conversionConstant) {
0607         emit conversionConstantChanged(d->conversionConstant, oldConversionConstant);
0608     }
0609 }
0610 
0611 void KisSpinBoxUnitManager::grantDocumentRelativeUnits()
0612 {
0613     d->canAccessDocument = true;
0614 }