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 }