1 /*
2 * Copyright (c) 2017 Laurent Valentin Jospin <laurent.valentin@famillejospin.ch>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19 #include "kis_spin_box_unit_manager.h"
20
21 #include "KoUnit.h"
22 #include <klocalizedstring.h>
23
24 #include <QtMath>
25
26
27 KisSpinBoxUnitManagerBuilder* KisSpinBoxUnitManagerFactory::builder = nullptr;
28
buildDefaultUnitManager(QObject * parent)29 KisSpinBoxUnitManager* KisSpinBoxUnitManagerFactory::buildDefaultUnitManager(QObject* parent)
30 {
31 if (builder == nullptr) {
32 return new KisSpinBoxUnitManager(parent);
33 }
34
35 return builder->buildUnitManager(parent);
36 }
37
setDefaultUnitManagerBuilder(KisSpinBoxUnitManagerBuilder * pBuilder)38 void KisSpinBoxUnitManagerFactory::setDefaultUnitManagerBuilder(KisSpinBoxUnitManagerBuilder* pBuilder)
39 {
40 if (builder != nullptr) {
41 delete builder; //The factory took over the lifecycle of the builder, so it delete it when replaced.
42 }
43
44 builder = pBuilder;
45 }
46
clearUnitManagerBuilder()47 void KisSpinBoxUnitManagerFactory::clearUnitManagerBuilder()
48 {
49 if (builder != nullptr) {
50 delete builder; //The factory took over the lifecycle of the builder, so it delete it when replaced.
51 }
52
53 builder = nullptr;
54 }
55
56 const QStringList KisSpinBoxUnitManager::referenceUnitSymbols = {"pt", "px", "°", "frame"};
57
58 const QStringList KisSpinBoxUnitManager::documentRelativeLengthUnitSymbols = {"px", "vw", "vh"}; //px are relative to the resolution, vw and vh to the width and height.
59 const QStringList KisSpinBoxUnitManager::documentRelativeTimeUnitSymbols = {"s", "%"}; //secondes are relative to the framerate, % to the sequence length.
60
61 class Q_DECL_HIDDEN KisSpinBoxUnitManager::Private
62 {
63 public:
Private(KisSpinBoxUnitManager::UnitDimension pDim=KisSpinBoxUnitManager::LENGTH,QString pUnitSymbol="pt",double pConv=1.0)64 Private(KisSpinBoxUnitManager::UnitDimension pDim = KisSpinBoxUnitManager::LENGTH,
65 QString pUnitSymbol = "pt",
66 double pConv = 1.0):
67 dim(pDim),
68 unitSymbol(pUnitSymbol),
69 conversionFactor(pConv)
70 {
71
72 }
73
74 KisSpinBoxUnitManager::UnitDimension dim;
75
76 QString unitSymbol;
77 mutable double conversionFactor;
78 bool conversionFactorIsFixed {true}; //tell if it's possible to trust the conversion factor stored or if it's needed to recompute it.
79 mutable double conversionConstant {0};
80 bool conversionConstantIsFixed {true}; //tell if it's possible to trust the conversion constant stored or if it's needed to recompute it.
81
82 KisSpinBoxUnitManager::Constrains constrains { KisSpinBoxUnitManager::NOCONSTR };
83
84 mutable QStringList unitList;
85 mutable bool unitListCached {false};
86
87 mutable QStringList unitListWithName;
88 mutable bool unitListWithNameCached {false};
89
90 //it's possible to store a reference for the % unit, for length.
91 bool hasHundredPercent {false};
92 qreal hundredPercent {0};
93
94 bool canAccessDocument {false};
95
96 QVector<KisSpinBoxUnitManager*> connectedUnitManagers;
97 };
98
KisSpinBoxUnitManager(QObject * parent)99 KisSpinBoxUnitManager::KisSpinBoxUnitManager(QObject *parent) : QAbstractListModel(parent)
100 {
101 d = new Private();
102
103 connect(this, (void (KisSpinBoxUnitManager::*)( QString )) &KisSpinBoxUnitManager::unitChanged, this, &KisSpinBoxUnitManager::newUnitSymbolToUnitIndex);
104 }
~KisSpinBoxUnitManager()105 KisSpinBoxUnitManager::~KisSpinBoxUnitManager()
106 {
107 delete d;
108 }
109
getUnitDimensionType() const110 int KisSpinBoxUnitManager::getUnitDimensionType() const
111 {
112 return d->dim;
113 }
114
getReferenceUnitSymbol() const115 QString KisSpinBoxUnitManager::getReferenceUnitSymbol() const
116 {
117 return referenceUnitSymbols[d->dim];
118 }
119
getApparentUnitSymbol() const120 QString KisSpinBoxUnitManager::getApparentUnitSymbol() const
121 {
122 return d->unitSymbol;
123 }
124
getApparentUnitId() const125 int KisSpinBoxUnitManager::getApparentUnitId() const
126 {
127 QStringList list = getsUnitSymbolList();
128 return list.indexOf(d->unitSymbol);
129 }
130
getApparentUnitRecommandedDecimals() const131 int KisSpinBoxUnitManager::getApparentUnitRecommandedDecimals() const {
132
133 switch (d->dim) {
134
135 case LENGTH:
136 if (d->unitSymbol == "px") {
137 return 0;
138 } else {
139 return 2;
140 }
141 case IMLENGTH:
142 if (d->unitSymbol == "px") {
143 return 0;
144 } else {
145 return 2;
146 }
147 default: //by default return 2.
148 break;
149 }
150
151 return 2;
152
153 }
154
getsUnitSymbolList(bool withName) const155 QStringList KisSpinBoxUnitManager::getsUnitSymbolList(bool withName) const{
156
157 QStringList list;
158
159 if (withName) {
160 if (d->unitListWithNameCached) {
161 return d->unitListWithName;
162 }
163 } else {
164 if (d->unitListCached) {
165 return d->unitList;
166 }
167 }
168
169 switch (d->dim) {
170
171 case LENGTH:
172
173 for (int i = 0; i < KoUnit::TypeCount; i++) {
174
175 if (KoUnit::Type(i) == KoUnit::Pixel) {
176 continue; //skip pixel, which is a document relative unit, in the base class.
177 }
178
179 if (withName) {
180 list << KoUnit::unitDescription(KoUnit::Type(i));
181 } else {
182 list << KoUnit(KoUnit::Type(i)).symbol();
183 }
184 }
185
186 if (hasPercent(LENGTH)) {
187
188 if (withName) {
189 list << i18n("percent (%)");
190 } else {
191 list << "%";
192 }
193
194 }
195
196 if (d->canAccessDocument) {
197 // ad document relative units
198 if (withName) {
199 list << KoUnit::unitDescription(KoUnit::Pixel) << i18n("percent of view width (vw)") << i18n("percent of view height (vh)");
200 } else {
201 list << documentRelativeLengthUnitSymbols;
202 }
203 }
204
205 break;
206
207 case IMLENGTH:
208
209 if (withName) {
210 list << KoUnit::unitDescription(KoUnit::Pixel);
211 } else {
212 list << "px";
213 }
214
215 if (hasPercent(IMLENGTH)) {
216
217 if (withName) {
218 list << i18n("percent (%)");
219 } else {
220 list << "%";
221 }
222
223 }
224
225 if (d->canAccessDocument) {
226 // ad document relative units
227 if (withName) {
228 list << i18n("percent of view width (vw)") << i18n("percent of view height (vh)");
229 } else {
230 list << "vw" << "vh";
231 }
232 }
233 break;
234
235 case ANGLE:
236
237 if (withName) {
238 list << i18n("degrees (°)") << i18n("radians (rad)") << i18n("gons (gon)") << i18n("percent of circle (%)");
239 } else {
240 list << "°" << "rad" << "gon" << "%";
241 }
242 break;
243
244 case TIME:
245
246 if (withName) {
247 list << i18n("frames (f)");
248 } else {
249 list << "f";
250 }
251
252 if (d->canAccessDocument) {
253 if (withName) {
254 list << i18n("seconds (s)") << i18n("percent of animation (%)");
255 } else {
256 list << documentRelativeTimeUnitSymbols;
257 }
258 }
259
260 break;
261
262 }
263
264 if (withName) {
265 d->unitListWithName = list;
266 d->unitListWithNameCached = true;
267 } else {
268 d->unitList = list;
269 d->unitListCached = true;
270 }
271
272 return list;
273
274 }
275
getConversionConstant(int dim,QString symbol) const276 qreal KisSpinBoxUnitManager::getConversionConstant(int dim, QString symbol) const
277 {
278 Q_UNUSED(dim);
279 Q_UNUSED(symbol);
280
281 return 0; // all units managed here are transform via a linear function, so this will always be 0 in this class.
282 }
283
getReferenceValue(double apparentValue) const284 qreal KisSpinBoxUnitManager::getReferenceValue(double apparentValue) const
285 {
286 if (!d->conversionFactorIsFixed) {
287 recomputeConversionFactor();
288 }
289
290 if(!d->conversionConstantIsFixed) {
291 recomputeConvesrionConstant();
292 }
293
294 qreal v = (apparentValue - d->conversionConstant)/d->conversionFactor;
295
296 if (d->constrains &= REFISINT) {
297 v = qFloor(v);
298 }
299
300 return v;
301
302 }
303
rowCount(const QModelIndex & parent) const304 int KisSpinBoxUnitManager::rowCount(const QModelIndex &parent) const {
305 if (parent == QModelIndex()) {
306 return getsUnitSymbolList().size();
307 }
308 return 0;
309 }
310
data(const QModelIndex & index,int role) const311 QVariant KisSpinBoxUnitManager::data(const QModelIndex &index, int role) const {
312
313 if (role == Qt::DisplayRole) {
314
315 return getsUnitSymbolList(false).at(index.row());
316
317 } else if (role == Qt::ToolTipRole) {
318
319 return getsUnitSymbolList(true).at(index.row());
320
321 }
322
323 return QVariant();
324 }
325
getApparentValue(double refValue) const326 qreal KisSpinBoxUnitManager::getApparentValue(double refValue) const
327 {
328 if (!d->conversionFactorIsFixed) {
329 recomputeConversionFactor();
330 }
331
332 if(!d->conversionConstantIsFixed) {
333 recomputeConvesrionConstant();
334 }
335
336 qreal v = refValue*d->conversionFactor + d->conversionConstant;
337
338 if (d->constrains &= VALISINT) {
339 v = qFloor(v);
340 }
341
342 return v;
343 }
344
getConversionFactor(int dim,QString symbol) const345 qreal KisSpinBoxUnitManager::getConversionFactor(int dim, QString symbol) const
346 {
347
348 qreal factor = -1;
349
350 switch (dim) {
351
352 case LENGTH:
353 do {
354 if (symbol == "px") {
355 break;
356 }
357
358 bool ok;
359 KoUnit unit = KoUnit::fromSymbol(symbol, &ok);
360 if (! ok) {
361 break;
362 }
363 factor = unit.toUserValuePrecise(1.0); // use the precise function
364 } while (0) ;
365 break;
366
367 case IMLENGTH:
368 if (symbol == "px") {
369 factor = 1;
370 }
371 break;
372
373 case ANGLE:
374 if (symbol == "°") {
375 factor = 1.0;
376 break;
377 }
378 if (symbol == "rad") {
379 factor = acos(-1)/90.0;
380 break;
381 }
382 if (symbol == "gon") {
383 factor = 10.0/9.0;
384 break;
385 }
386 if (symbol == "%") {
387 factor = 2.5/9.0; //(25% of circle is 90°)
388 break;
389 }
390 break;
391
392 case TIME:
393
394 if (symbol != "f") { //we have only frames for the moment.
395 break;
396 }
397 factor = 1.0;
398 break;
399
400 default:
401 break;
402 }
403
404 return factor;
405 }
406
407
setUnitDimension(UnitDimension dimension)408 void KisSpinBoxUnitManager::setUnitDimension(UnitDimension dimension)
409 {
410 if (dimension == d->dim) {
411 return;
412 }
413
414 d->dim = dimension;
415 d->unitSymbol = referenceUnitSymbols[d->dim]; //Active dim is reference dim when just changed.
416 d->conversionFactor = 1.0;
417
418 emit unitDimensionChanged(d->dim);
419
420 }
421
setApparentUnitFromSymbol(QString pSymbol)422 void KisSpinBoxUnitManager::setApparentUnitFromSymbol(QString pSymbol)
423 {
424
425 QString symbol = pSymbol.trimmed();
426
427 if (symbol == d->unitSymbol) {
428 return;
429 }
430
431 emit unitAboutToChange();
432
433 QString newSymb = "";
434
435 switch (d->dim) {
436
437 case ANGLE:
438 if (symbol.toLower() == "deg") {
439 newSymb = "°";
440 break;
441 }
442 goto default_indentifier; //always do default after handling possible special cases.
443
444 default_indentifier:
445 default:
446 QStringList list = getsUnitSymbolList();
447 if (list.contains(symbol, Qt::CaseInsensitive)) {
448 for (QString str : list) {
449 if (str.toLower() == symbol.toLower()) {
450 newSymb = str; //official symbol may contain capitals letters, so better take the official version.
451 break;
452 }
453 }
454 break;
455 }
456
457 }
458
459 if(newSymb.isEmpty()) {
460 return; //abort if it was impossible to locate the correct symbol.
461 }
462
463 if (d->canAccessDocument) {
464 //manage document relative units.
465
466 QStringList speUnits;
467
468 switch (d->dim) {
469
470 case LENGTH:
471 speUnits = documentRelativeLengthUnitSymbols;
472 goto default_identifier_conv_fact;
473
474 case IMLENGTH:
475 speUnits << "vw" << "vh";
476 goto default_identifier_conv_fact;
477
478 case TIME:
479 speUnits = documentRelativeTimeUnitSymbols;
480 goto default_identifier_conv_fact;
481
482 default_identifier_conv_fact:
483 default:
484
485 if (speUnits.isEmpty()) {
486 d->conversionFactorIsFixed = true;
487 break;
488 }
489
490 if (speUnits.contains(newSymb)) {
491 d->conversionFactorIsFixed = false;
492 break;
493 }
494
495 d->conversionFactorIsFixed = true;
496 break;
497 }
498
499 if (d->dim == TIME) {
500 if (newSymb == "%") {
501 d->conversionConstantIsFixed = false;
502 }
503 } else {
504 d->conversionConstantIsFixed = true;
505 }
506
507 }
508
509 qreal conversFact = getConversionFactor(d->dim, newSymb);
510 qreal oldConversFact = d->conversionFactor;
511
512 d->conversionFactor = conversFact;
513 emit conversionFactorChanged(d->conversionFactor, oldConversFact);
514
515 d->unitSymbol = newSymb;
516 emit unitChanged(newSymb);
517
518 }
519
selectApparentUnitFromIndex(int index)520 void KisSpinBoxUnitManager::selectApparentUnitFromIndex(int index) {
521
522 if (index >= 0 && index < rowCount()) {
523 setApparentUnitFromSymbol(getsUnitSymbolList().at(index));
524 }
525
526 }
527
528
syncWithOtherUnitManager(KisSpinBoxUnitManager * other)529 void KisSpinBoxUnitManager::syncWithOtherUnitManager(KisSpinBoxUnitManager* other) {
530
531 if (d->connectedUnitManagers.indexOf(other) >= 0) {
532 return;
533 }
534
535 if (other->getUnitDimensionType() == getUnitDimensionType()) { //sync only unitmanager of the same type.
536 if (other->getsUnitSymbolList() == getsUnitSymbolList()) { //and if we have identical units available.
537
538 connect(this, SIGNAL(unitChanged(int)), other, SLOT(selectApparentUnitFromIndex(int))); //sync units.
539 connect(other, SIGNAL(unitChanged(int)), this, SLOT(selectApparentUnitFromIndex(int))); //sync units.
540
541 d->connectedUnitManagers.append(other);
542
543 }
544 }
545
546 }
547
clearSyncWithOtherUnitManager(KisSpinBoxUnitManager * other)548 void KisSpinBoxUnitManager::clearSyncWithOtherUnitManager(KisSpinBoxUnitManager* other) {
549
550 int id = d->connectedUnitManagers.indexOf(other);
551
552 if (id < 0) {
553 return;
554 }
555
556 disconnect(this, SIGNAL(unitChanged(int)), other, SLOT(selectApparentUnitFromIndex(int))); //unsync units.
557 disconnect(other, SIGNAL(unitChanged(int)), this, SLOT(selectApparentUnitFromIndex(int))); //unsync units.
558
559 d->connectedUnitManagers.removeAt(id);
560
561 }
562
newUnitSymbolToUnitIndex(QString symbol)563 void KisSpinBoxUnitManager::newUnitSymbolToUnitIndex(QString symbol) {
564 int id = getsUnitSymbolList().indexOf(symbol);
565
566 if (id >= 0) {
567 emit unitChanged(id);
568 }
569 }
570
hasPercent(int unitDim) const571 bool KisSpinBoxUnitManager::hasPercent(int unitDim) const {
572
573 if (unitDim == IMLENGTH || unitDim == LENGTH) {
574 return false;
575 }
576
577 if (unitDim == TIME) {
578 return d->canAccessDocument;
579 }
580
581 if (unitDim == ANGLE) {
582 return true; //percent is fixed when considering angles.
583 }
584
585 return false;
586 }
587
recomputeConversionFactor() const588 void KisSpinBoxUnitManager::recomputeConversionFactor() const
589 {
590 if (d->conversionFactorIsFixed) {
591 return;
592 }
593
594 qreal oldConversionFactor = d->conversionFactor;
595
596 d->conversionFactor = getConversionFactor(d->dim, d->unitSymbol);
597
598 if (oldConversionFactor != d->conversionFactor) {
599 emit conversionFactorChanged(d->conversionFactor, oldConversionFactor);
600 }
601 }
602
recomputeConvesrionConstant() const603 void KisSpinBoxUnitManager::recomputeConvesrionConstant() const
604 {
605 if (d->conversionConstantIsFixed) {
606 return;
607 }
608
609 qreal oldConversionConstant = d->conversionConstant;
610
611 d->conversionConstant = getConversionConstant(d->dim, d->unitSymbol);
612
613 if (oldConversionConstant != d->conversionConstant) {
614 emit conversionConstantChanged(d->conversionConstant, oldConversionConstant);
615 }
616 }
617
grantDocumentRelativeUnits()618 void KisSpinBoxUnitManager::grantDocumentRelativeUnits()
619 {
620 d->canAccessDocument = true;
621 }
622