Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_double_parse_unit_spin_box.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2016 Laurent Valentin Jospin <laurent.valentin@famillejospin.ch>
3 * SPDX-FileCopyrightText: 2021 Deif Lou <ginoba@gmail.com>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
10#include <klocalizedstring.h>
11
12#include <QLineEdit>
13#include <QMenu>
14#include <QAction>
15#include <QtMath>
16#include <QRegularExpression>
17#include <QString>
18#include <QActionGroup>
19
21{
22public:
23 Private(double low, double up, double step, KisSpinBoxUnitManager* unitManager)
24 : lowerInPoints(low),
25 upperInPoints(up),
26 stepInPoints(step),
27 unit(KoUnit(KoUnit::Point)),
28 outPutSymbol(""),
29 unitManager(unitManager),
30 defaultUnitManager(unitManager)
31 {
32 }
33
34 double minStepForPrec {0};
35 double lowerInPoints {0.0};
36 double upperInPoints {0.0};
37 double stepInPoints {0.0};
39
40 double previousValueInPoint {0.0};
42 QString outPutSymbol;
43
44 KisSpinBoxUnitManager* unitManager {0}; //manage more units than permitted by KoUnit.
45 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.
46
47 bool isDeleting {false};
48
49 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.
50 bool letUnitBeChangedFromOutsideMoreThanOnce {true};
51
52 bool displayUnit {true};
53
54 bool allowResetDecimals {true};
55
56 bool mustUsePreviousText {false};
57};
58
61 d(new Private(-9999, 9999, 1, KisSpinBoxUnitManagerFactory::buildDefaultUnitManager(this)))
62{
64 setAlignment( Qt::AlignRight );
65
66 connect(this, SIGNAL(valueChanged(double)), this, SLOT(privateValueChanged()));
67 connect(lineEdit(), SIGNAL(textChanged(QString)),
68 this, SLOT(detectUnitChanges()) );
69
71 connect(d->unitManager, (void (KisSpinBoxUnitManager::*)( QString )) &KisSpinBoxUnitManager::unitChanged, this, (void (KisDoubleParseUnitSpinBox::*)( QString const& )) &KisDoubleParseUnitSpinBox::internalUnitChange);
72
73 setDecimals(d->unitManager->getApparentUnitRecommendedDecimals());
74}
75
77{
78 d->isDeleting = true;
79 delete d->defaultUnitManager;
80 delete d;
81}
82
84{
85 if (unitManager == d->unitManager) {
86 // just in case we're trying to set manager with the current one...
87 return;
88 }
89
90 KisSpinBoxUnitManager* oldUnitManager = 0;
91
92 if (d->unitManager) {
93 // current unit manager is still here (then setUnitManager not call because it has been destroyed)
94 //
95 oldUnitManager = d->unitManager;
96
97 disconnect(oldUnitManager, &QObject::destroyed,
98 this, &KisDoubleParseUnitSpinBox::disconnectExternalUnitManager); //there's no dependence anymore.
99 disconnect(oldUnitManager, (void (KisSpinBoxUnitManager::*)()) &KisSpinBoxUnitManager::unitAboutToChange,
101 disconnect(oldUnitManager, (void (KisSpinBoxUnitManager::*)( QString )) &KisSpinBoxUnitManager::unitChanged,
103 }
104
105 d->unitManager = unitManager;
106
107
108 // decimals must be set before value, otherwise value/step/min/max values will be rounded to previous unit decimals value
109 if (d->allowResetDecimals) { //if the user has not fixed the number of decimals.
110 setDecimals(d->unitManager->getApparentUnitRecommendedDecimals());
111 }
112
113 qreal newVal = 0.0;
114
115 double newMin;
116 double newMax;
117 double newStep;
118
119 if (oldUnitManager == 0 ||
120 (oldUnitManager &&
121 (d->unitManager->getApparentUnitSymbol() != oldUnitManager->getApparentUnitSymbol() ||
122 d->unitManager->getUnitDimensionType() == oldUnitManager->getUnitDimensionType()))) {
123
124 if (oldUnitManager && d->unitManager->getUnitDimensionType() == oldUnitManager->getUnitDimensionType()) {
125 //dimension is the same, calculate the new value
126 newVal = d->unitManager->getApparentValue(oldUnitManager->getReferenceValue(KisDoubleParseSpinBox::value()));
127 } else {
128 newVal = d->unitManager->getApparentValue(d->lowerInPoints);
129 }
130
131 newMin = d->unitManager->getApparentValue(d->lowerInPoints);
132 newMax = d->unitManager->getApparentValue(d->upperInPoints);
133 newStep = d->unitManager->getApparentValue(d->stepInPoints);
134
135 if (d->unitManager->getApparentUnitSymbol() == KoUnit(KoUnit::Pixel).symbol()) {
136 // limit the pixel step by 1.0
137 newStep = 1.0;
138 }
139
140 KisDoubleParseSpinBox::setMinimum(newMin);
141 KisDoubleParseSpinBox::setMaximum(newMax);
142 KisDoubleParseSpinBox::setSingleStep(newStep);
143 }
144
145 connect(d->unitManager, &QObject::destroyed,
149 connect(d->unitManager, (void (KisSpinBoxUnitManager::*)( QString )) &KisSpinBoxUnitManager::unitChanged,
151
153}
154
156{
157 double apparentValue;
158 double fact = 0.0;
159 double cons = 0.0;
160
161 if (d->outPutSymbol.isEmpty()) {
162 apparentValue = d->unitManager->getApparentValue(newValue);
163 } else {
164
165 fact = d->unitManager->getConversionFactor(d->unitManager->getUnitDimensionType(), d->outPutSymbol);
166 cons = d->unitManager->getConversionConstant(d->unitManager->getUnitDimensionType(), d->outPutSymbol);
167
168 apparentValue = fact*newValue + cons;
169 }
170
171 if (apparentValue == KisDoubleParseSpinBox::value()) {
172 return;
173 }
174
175 if (d->outPutSymbol.isEmpty()) {
176 KisDoubleParseSpinBox::setValue( apparentValue );
177 } else {
178 KisDoubleParseSpinBox::setValue( d->unitManager->getApparentValue((newValue - cons)/fact) );
179 }
180}
181
183{
184 double apparentValue = d->unitManager->getApparentValue(newValue);
185
186 if (apparentValue == KisDoubleParseSpinBox::value()) {
187 return;
188 }
189 KisDoubleParseSpinBox::setValue( apparentValue );
190}
191
192
194{
195 if (d->unitHasBeenChangedFromOutSideOnce && !d->letUnitBeChangedFromOutsideMoreThanOnce) {
196 return;
197 }
198
199 if (d->unitManager->getUnitDimensionType() != KisSpinBoxUnitManager::LENGTH) {
200 d->unitManager->setUnitDimension(KisSpinBoxUnitManager::LENGTH); //setting the unit using a KoUnit mean you want to use a length.
201 }
202
204 d->unit = unit;
205}
206
207void KisDoubleParseUnitSpinBox::setUnit(const QString &symbol)
208{
209 d->unitManager->setApparentUnitFromSymbol(symbol); //via signals and slots, the correct functions should be called.
210}
211
213{
214 d->outPutSymbol = symbol;
215}
216
218{
219 return d->outPutSymbol;
220}
221
223
224 d->previousValueInPoint = d->unitManager->getReferenceValue(KisDoubleParseSpinBox::value());
225 d->previousSymbol = d->unitManager->getApparentUnitSymbol();
226}
227
229 //d->unitManager->setApparentUnitFromSymbol(symbol);
230
231 if (d->unitManager->getApparentUnitSymbol() == d->previousSymbol) { //the setApparentUnitFromSymbol is a bit clever, for example in regard of Casesensitivity. So better check like this.
232 return;
233 }
234
235 // decimals must be updated before value/step/min/max otherwise they'll rounded with previous unit decimal value
236 if (d->allowResetDecimals) {
237 setDecimals(d->unitManager->getApparentUnitRecommendedDecimals());
238 }
239
240 KisDoubleParseSpinBox::setMinimum( d->unitManager->getApparentValue( d->lowerInPoints ) );
241 KisDoubleParseSpinBox::setMaximum( d->unitManager->getApparentValue( d->upperInPoints ) );
242
243 qreal step = d->unitManager->getApparentValue( d->stepInPoints );
244
245 if (symbol == KoUnit(KoUnit::Pixel).symbol()) {
246 // limit the pixel step by 1.0
247 step = 1.0;
248 }
249
250 setSingleStep( step );
251 KisDoubleParseSpinBox::setValue( d->unitManager->getApparentValue( d->previousValueInPoint ) );
252
253 d->unitHasBeenChangedFromOutSideOnce = true;
254}
255
257{
259 return;
260 }
261
262 d->unitManager->setUnitDimension((KisSpinBoxUnitManager::UnitDimension) dim);
263}
264
266{
267 if (d->outPutSymbol.isEmpty()) {
268 return d->unitManager->getReferenceValue( KisDoubleParseSpinBox::value() );
269 }
270
271 double ref = d->unitManager->getReferenceValue( KisDoubleParseSpinBox::value() );
272 double fact = d->unitManager->getConversionFactor(d->unitManager->getUnitDimensionType(), d->outPutSymbol);
273 double cons = d->unitManager->getConversionConstant(d->unitManager->getUnitDimensionType(), d->outPutSymbol);
274
275 return fact*ref + cons;
276}
277
279{
280 return d->unitManager->getReferenceValue( KisDoubleParseSpinBox::value() );
281}
282
283void KisDoubleParseUnitSpinBox::setValuePt(double value, bool overwriteExpression)
284{
285 KisDoubleParseSpinBox::setValue(d->unitManager->getApparentValue(value), overwriteExpression);
286}
287
289{
290 d->lowerInPoints = d->unitManager->getReferenceValue(min);
291 KisDoubleParseSpinBox::setMinimum( min );
292}
293
295{
296 d->lowerInPoints = min;
297 KisDoubleParseSpinBox::setMinimum( d->unitManager->getApparentValue( min ) );
298}
299
300
302{
303 d->upperInPoints = d->unitManager->getReferenceValue(max);
304 KisDoubleParseSpinBox::setMaximum( max );
305}
306
308{
309 d->upperInPoints = max;
310 KisDoubleParseSpinBox::setMaximum( d->unitManager->getApparentValue( max ) );
311}
312
314{
315 d->stepInPoints = d->unitManager->getReferenceValue(step);
316 KisDoubleParseSpinBox::setSingleStep( step );
317}
318
320{
321 d->stepInPoints = step;
322 KisDoubleParseSpinBox::setSingleStep( d->unitManager->getApparentValue( step ) );
323}
324
325void KisDoubleParseUnitSpinBox::setMinMaxStep( double min, double max, double step )
326{
327 setMinimum( min );
328 setMaximum( max );
329 setLineStep( step );
330}
331
332void KisDoubleParseUnitSpinBox::setMinMaxStepPt( double min, double max, double step )
333{
334 setMinimumPt( min );
335 setMaximumPt( max );
336 setLineStepPt( step );
337}
338
339
341{
342 // Just return the current value (for example when the user is editing)
343 if (d->mustUsePreviousText) {
344 return cleanText();
345 }
346 // Construct a new value
348 if (d->displayUnit) {
349 if (!txt.endsWith(d->unitManager->getApparentUnitSymbol())) {
350 txt += " " + d->unitManager->getApparentUnitSymbol();
351 }
352 }
353 return txt;
354}
355
357{
358 return makeTextClean(cleanText());
359}
360
361double KisDoubleParseUnitSpinBox::valueFromText( const QString& str ) const
362{
363 QString txt = makeTextClean(str);
364 //this function will take care of prefix (and don't mind if suffix has been removed.
366}
367
369{
370 d->letUnitBeChangedFromOutsideMoreThanOnce = toggle;
371}
372
374{
375 d->displayUnit = toggle;
376}
377
379{
380 d->allowResetDecimals = !prevent;
381}
382
387
389{
390 QString str = veryCleanText().trimmed(); //text with the new unit but not the old one.
391
392 QRegularExpression regexp ("([ ]*[a-zA-Z]+[ ]*)$"); // Letters or spaces at end
393 int res = str.indexOf( regexp );
394
395 if (res > -1) {
396 QString expr ( str.right( str.size() - res ) );
397 expr = expr.trimmed();
398 return expr;
399 }
400
401 return "";
402}
403
405{
406 QString unitSymb = detectUnit();
407
408 if (unitSymb.isEmpty()) {
409 return;
410 }
411
412 QString oldUnitSymb = d->unitManager->getApparentUnitSymbol();
413
414 setUnit(unitSymb);
415 // Quick hack
416 // This function is called when the user changed the text and the call to
417 // setValue will provoke a call to textFromValue which will return a new
418 // text different from the current one. Since the following setValue is
419 // called because of a user change, we use a flag to prevent the text from
420 // changing
421 d->mustUsePreviousText = true;
422 // Change value keep the old value, but converted to new unit... which is
423 // different from the value the user entered in the new unit. So we need
424 // to set the new value.
425 setValue(valueFromText(cleanText()));
426 d->mustUsePreviousText = false;
427
428 if (oldUnitSymb != d->unitManager->getApparentUnitSymbol()) {
429 // the user has changed the unit, so we block changes from outside.
431 }
432}
433
434QString KisDoubleParseUnitSpinBox::makeTextClean(QString const& txt) const
435{
436 QString expr = txt;
437 QString symbol = d->unitManager->getApparentUnitSymbol();
438
439 if ( expr.endsWith(suffix()) ) {
440 expr.remove(expr.size()-suffix().size(), suffix().size());
441 }
442
443 expr = expr.trimmed();
444
445 if ( expr.endsWith(symbol) ) {
446 expr.remove(expr.size()-symbol.size(), symbol.size());
447 }
448
449 return expr.trimmed();
450}
451
453{
454 // Internal disconnectExternalUnitManager() method is called when the current unit manager d->unitManager
455 // has been destroyed
456 // --> ensure the d->unitManager does not point to anything anymore
457 d->unitManager = 0;
458
459 if (!d->isDeleting)
460 {
461 setUnitManager(d->defaultUnitManager); //go back to default unit manager.
462 }
463}
464
466{
467 KisDoubleParseSpinBox::setDecimals(prec);
468 d->minStepForPrec = 1/qPow(10, prec);
469
470 // fix current single step value is needed
471 setSingleStep(singleStep());
472}
473
475{
476 // ensure step value is never below minimal value for precision
477 KisDoubleParseSpinBox::setSingleStep(qMax(d->minStepForPrec, val));
478}
479
480
482{
483 // default standard menu for line edit, not possible to get the default menu from a QSpinBox
484 QMenu* menu = lineEdit()->createStandardContextMenu();
485 if (!menu)
486 return;
487
488 // then need to recreate "Step Up" and "Step Down" actions
489 menu->addSeparator();
490 const uint se = stepEnabled();
491 QAction *up = menu->addAction(tr("&Step up"));
492 up->setEnabled(se & StepUpEnabled);
493 QAction *down = menu->addAction(tr("Step &down"));
494 down->setEnabled(se & StepDownEnabled);
495 menu->addSeparator();
496
497 // and add expected new entries: menu/submenu with Units
498 QMenu* menuUnit = menu->addMenu(i18n("Unit"));
499 QActionGroup* unitActions = new QActionGroup(this);
500 Q_FOREACH(QString unitSymbol, d->unitManager->getsUnitSymbolList(false)) {
501 QString unitLabel = KoUnit::unitDescription(KoUnit::fromSymbol(unitSymbol).type());
502
503 // need to check symbol not managed by KoUnit (return "Points (pt)" in this case...)
504 switch (d->unitManager->getUnitDimensionType()) {
507 if (unitSymbol == "%") {
508 unitLabel = i18n("Percent (%)");
509 } else if (unitSymbol == "vw") {
510 unitLabel = i18n("percent of view width (vw)");
511 } else if (unitSymbol == "vh") {
512 unitLabel = i18n("percent of view height (vh)");
513 }
514 break;
515
517 if (unitSymbol == "°") {
518 unitLabel = i18n("degrees (°)");
519 } else if (unitSymbol == "rad") {
520 unitLabel = i18n("radians (rad)");
521 } else if (unitSymbol == "gon") {
522 unitLabel = i18n("gons (gon)");
523 } else if (unitSymbol == "%") {
524 unitLabel = i18n("percent of circle (%)");
525 }
526 break;
527
529 if (unitSymbol == "f") {
530 unitLabel = i18n("frames (f)");
531 } else if (unitSymbol == "s") {
532 unitLabel = i18n("seconds (s)");
533 } else if (unitSymbol == "%") {
534 unitLabel = i18n("percent of animation (%)");
535 }
536 break;
537 }
538
539 QAction *unitAction = menuUnit->addAction(unitLabel);
540 unitAction->setProperty("symbol", unitSymbol);
541 unitAction->setCheckable(true);
542 unitAction->setActionGroup(unitActions);
543 unitAction->setChecked(unitSymbol == d->unitManager->getApparentUnitSymbol());
544 }
545
546 const QPoint pos = (event->reason() == QContextMenuEvent::Mouse)
547 ? event->globalPos() : mapToGlobal(QPoint(event->pos().x(), 0)) + QPoint(width() / 2, height() / 2);
548 const QAction *action = menu->exec(pos);
549
550 if (action) {
551 if (action == up) {
552 stepBy(1);
553 } else if (action == down) {
554 stepBy(-1);
555 } else {
556 QVariant symbol = action->property("symbol");
557 if (symbol.isValid()) {
558 d->unitManager->setApparentUnitFromSymbol(symbol.toString());
559 }
560 }
561 }
562
563 delete static_cast<QMenu *>(menuUnit);
564 delete static_cast<QMenu *>(menu);
565 event->accept();
566}
567
float value(const T *src, size_t ch)
unsigned int uint
The KisDoubleParseSpinBox class is a cleverer doubleSpinBox, able to parse arithmetic expressions.
void setValue(double value, bool overwriteExpression=false)
Set the value of the spinbox.
QString textFromValue(double value) const override
void stepBy(int steps) override
This is a reimplementation of QDoubleSpinBox::stepBy that uses setValue.
double valueFromText(const QString &text) const override
The KisDoubleParseUnitSpinBox class is an evolution of the.
void setMinimumPt(double min)
Set minimum value in points.
void setLineStepPt(double step)
Set step size in points.
QString makeTextClean(QString const &txt) const
void setLineStep(double step)
Set step size in the current unit.
void setMinMaxStep(double min, double max, double step)
Set minimum, maximum value and the step size (in current unit)
void setValuePt(double value, bool overWriteExpression=false)
Sets the value in points.
virtual void setDimensionType(int dim)
setDimensionType set the dimension (for example length or angle) of the units the spinbox manage
void setUnitManager(KisSpinBoxUnitManager *unitManager)
void contextMenuEvent(QContextMenuEvent *event) override
void setReturnUnit(const QString &symbol)
setReturnUnit set a unit, such that the spinbox now return values in this unit instead of the referen...
void internalUnitChange(QString const &symbol)
change the unit, reset the spin box every time. From the outside it's always set unit that should be ...
void setMaximum(double max)
Set maximum value in current unit.
virtual void setUnit(const KoUnit &unit)
double valueFromText(const QString &str) const override
void setMinimum(double min)
Set minimum value in current unit.
void setMinMaxStepPt(double min, double max, double step)
Set minimum, maximum value and the step size (all in points)
void setDisplayUnit(bool toggle)
display the unit symbol in the spinbox or not. For example if the unit is displayed in a combobox con...
virtual void changeValue(double newValue)
void setMaximumPt(double max)
Set maximum value in points.
QString returnUnit() const
returnUnit returns the unit in which values are returned
QString veryCleanText() const override
get the text in the spinbox without prefix or suffix, and remove unit symbol if present.
Private(double low, double up, double step, KisSpinBoxUnitManager *unitManager)
QString textFromValue(double value) const override
void valueChangedPt(qreal)
emitted like valueChanged in the parent, but this one emits the point value, or converted to another ...
The KisSpinBoxUnitManagerFactory class is a factory that is used to build a default KisSpinBoxUnitMan...
The KisSpinBoxUnitManager class is an abstract interface for the unitspinboxes classes to manage diff...
qreal getReferenceValue(double apparentValue) const
static bool isUnitId(int code)
void unitChanged(QString symbol)
static QString unitDescription(KoUnit::Type type)
Get the description string of the given unit.
Definition KoUnit.cpp:32
static KoUnit fromSymbol(const QString &symbol, bool *ok=0)
Definition KoUnit.cpp:271
QString symbol() const
Get the symbol string of the unit.
Definition KoUnit.cpp:347
@ Point
Postscript point, 1/72th of an Inco.
Definition KoUnit.h:76
@ Pixel
Definition KoUnit.h:82
typedef void(QOPENGLF_APIENTRYP PFNGLINVALIDATEBUFFERDATAPROC)(GLuint buffer)