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
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,
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
284{
285 d->lowerInPoints = d->unitManager->getReferenceValue(min);
286 KisDoubleParseSpinBox::setMinimum( min );
287}
288
290{
291 d->lowerInPoints = min;
292 KisDoubleParseSpinBox::setMinimum( d->unitManager->getApparentValue( min ) );
293}
294
295
297{
298 d->upperInPoints = d->unitManager->getReferenceValue(max);
299 KisDoubleParseSpinBox::setMaximum( max );
300}
301
303{
304 d->upperInPoints = max;
305 KisDoubleParseSpinBox::setMaximum( d->unitManager->getApparentValue( max ) );
306}
307
309{
310 d->stepInPoints = d->unitManager->getReferenceValue(step);
311 KisDoubleParseSpinBox::setSingleStep( step );
312}
313
315{
316 d->stepInPoints = step;
317 KisDoubleParseSpinBox::setSingleStep( d->unitManager->getApparentValue( step ) );
318}
319
320void KisDoubleParseUnitSpinBox::setMinMaxStep( double min, double max, double step )
321{
322 setMinimum( min );
323 setMaximum( max );
324 setLineStep( step );
325}
326
327void KisDoubleParseUnitSpinBox::setMinMaxStepPt( double min, double max, double step )
328{
329 setMinimumPt( min );
330 setMaximumPt( max );
331 setLineStepPt( step );
332}
333
334
336{
337 // Just return the current value (for example when the user is editing)
338 if (d->mustUsePreviousText) {
339 return cleanText();
340 }
341 // Construct a new value
343 if (d->displayUnit) {
344 if (!txt.endsWith(d->unitManager->getApparentUnitSymbol())) {
345 txt += " " + d->unitManager->getApparentUnitSymbol();
346 }
347 }
348 return txt;
349}
350
352{
353 return makeTextClean(cleanText());
354}
355
356double KisDoubleParseUnitSpinBox::valueFromText( const QString& str ) const
357{
358 QString txt = makeTextClean(str);
359 //this function will take care of prefix (and don't mind if suffix has been removed.
361}
362
364{
365 d->letUnitBeChangedFromOutsideMoreThanOnce = toggle;
366}
367
369{
370 d->displayUnit = toggle;
371}
372
374{
375 d->allowResetDecimals = !prevent;
376}
377
382
384{
385 QString str = veryCleanText().trimmed(); //text with the new unit but not the old one.
386
387 QRegularExpression regexp ("([ ]*[a-zA-Z]+[ ]*)$"); // Letters or spaces at end
388 int res = str.indexOf( regexp );
389
390 if (res > -1) {
391 QString expr ( str.right( str.size() - res ) );
392 expr = expr.trimmed();
393 return expr;
394 }
395
396 return "";
397}
398
400{
401 QString unitSymb = detectUnit();
402
403 if (unitSymb.isEmpty()) {
404 return;
405 }
406
407 QString oldUnitSymb = d->unitManager->getApparentUnitSymbol();
408
409 setUnit(unitSymb);
410 // Quick hack
411 // This function is called when the user changed the text and the call to
412 // setValue will provoke a call to textFromValue which will return a new
413 // text different from the current one. Since the following setValue is
414 // called because of a user change, we use a flag to prevent the text from
415 // changing
416 d->mustUsePreviousText = true;
417 // Change value keep the old value, but converted to new unit... which is
418 // different from the value the user entered in the new unit. So we need
419 // to set the new value.
420 setValue(valueFromText(cleanText()));
421 d->mustUsePreviousText = false;
422
423 if (oldUnitSymb != d->unitManager->getApparentUnitSymbol()) {
424 // the user has changed the unit, so we block changes from outside.
426 }
427}
428
429QString KisDoubleParseUnitSpinBox::makeTextClean(QString const& txt) const
430{
431 QString expr = txt;
432 QString symbol = d->unitManager->getApparentUnitSymbol();
433
434 if ( expr.endsWith(suffix()) ) {
435 expr.remove(expr.size()-suffix().size(), suffix().size());
436 }
437
438 expr = expr.trimmed();
439
440 if ( expr.endsWith(symbol) ) {
441 expr.remove(expr.size()-symbol.size(), symbol.size());
442 }
443
444 return expr.trimmed();
445}
446
448{
449 // Internal disconnectExternalUnitManager() method is called when the current unit manager d->unitManager
450 // has been destroyed
451 // --> ensure the d->unitManager does not point to anything anymore
452 d->unitManager = 0;
453
454 if (!d->isDeleting)
455 {
456 setUnitManager(d->defaultUnitManager); //go back to default unit manager.
457 }
458}
459
461{
462 KisDoubleParseSpinBox::setDecimals(prec);
463 d->minStepForPrec = 1/qPow(10, prec);
464
465 // fix current single step value is needed
466 setSingleStep(singleStep());
467}
468
470{
471 // ensure step value is never below minimal value for precision
472 KisDoubleParseSpinBox::setSingleStep(qMax(d->minStepForPrec, val));
473}
474
475
477{
478 // default standard menu for line edit, not possible to get the default menu from a QSpinBox
479 QMenu* menu = lineEdit()->createStandardContextMenu();
480 if (!menu)
481 return;
482
483 // then need to recreate "Step Up" and "Step Down" actions
484 menu->addSeparator();
485 const uint se = stepEnabled();
486 QAction *up = menu->addAction(tr("&Step up"));
487 up->setEnabled(se & StepUpEnabled);
488 QAction *down = menu->addAction(tr("Step &down"));
489 down->setEnabled(se & StepDownEnabled);
490 menu->addSeparator();
491
492 // and add expected new entries: menu/submenu with Units
493 QMenu* menuUnit = menu->addMenu(i18n("Unit"));
494 QActionGroup* unitActions = new QActionGroup(this);
495 Q_FOREACH(QString unitSymbol, d->unitManager->getsUnitSymbolList(false)) {
496 QString unitLabel = KoUnit::unitDescription(KoUnit::fromSymbol(unitSymbol).type());
497
498 // need to check symbol not managed by KoUnit (return "Points (pt)" in this case...)
499 switch (d->unitManager->getUnitDimensionType()) {
502 if (unitSymbol == "%") {
503 unitLabel = i18n("Percent (%)");
504 } else if (unitSymbol == "vw") {
505 unitLabel = i18n("percent of view width (vw)");
506 } else if (unitSymbol == "vh") {
507 unitLabel = i18n("percent of view height (vh)");
508 }
509 break;
510
512 if (unitSymbol == "°") {
513 unitLabel = i18n("degrees (°)");
514 } else if (unitSymbol == "rad") {
515 unitLabel = i18n("radians (rad)");
516 } else if (unitSymbol == "gon") {
517 unitLabel = i18n("gons (gon)");
518 } else if (unitSymbol == "%") {
519 unitLabel = i18n("percent of circle (%)");
520 }
521 break;
522
524 if (unitSymbol == "f") {
525 unitLabel = i18n("frames (f)");
526 } else if (unitSymbol == "s") {
527 unitLabel = i18n("seconds (s)");
528 } else if (unitSymbol == "%") {
529 unitLabel = i18n("percent of animation (%)");
530 }
531 break;
532 }
533
534 QAction *unitAction = menuUnit->addAction(unitLabel);
535 unitAction->setProperty("symbol", unitSymbol);
536 unitAction->setCheckable(true);
537 unitAction->setActionGroup(unitActions);
538 unitAction->setChecked(unitSymbol == d->unitManager->getApparentUnitSymbol());
539 }
540
541 const QPoint pos = (event->reason() == QContextMenuEvent::Mouse)
542 ? event->globalPos() : mapToGlobal(QPoint(event->pos().x(), 0)) + QPoint(width() / 2, height() / 2);
543 const QAction *action = menu->exec(pos);
544
545 if (action) {
546 if (action == up) {
547 stepBy(1);
548 } else if (action == down) {
549 stepBy(-1);
550 } else {
551 QVariant symbol = action->property("symbol");
552 if (symbol.isValid()) {
553 d->unitManager->setApparentUnitFromSymbol(symbol.toString());
554 }
555 }
556 }
557
558 delete static_cast<QMenu *>(menuUnit);
559 delete static_cast<QMenu *>(menu);
560 event->accept();
561}
562
float value(const T *src, size_t ch)
unsigned int uint
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
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)
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)