FreeCAD

Форк
0
/
QuantitySpinBox.cpp 
974 строки · 27.5 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2014 Werner Mayer <wmayer[at]users.sourceforge.net>     *
3
 *                                                                         *
4
 *   This file is part of the FreeCAD CAx development system.              *
5
 *                                                                         *
6
 *   This library is free software; you can redistribute it and/or         *
7
 *   modify it under the terms of the GNU Library General Public           *
8
 *   License as published by the Free Software Foundation; either          *
9
 *   version 2 of the License, or (at your option) any later version.      *
10
 *                                                                         *
11
 *   This library  is distributed in the hope that it will be useful,      *
12
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
13
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
14
 *   GNU Library General Public License for more details.                  *
15
 *                                                                         *
16
 *   You should have received a copy of the GNU Library General Public     *
17
 *   License along with this library; see the file COPYING.LIB. If not,    *
18
 *   write to the Free Software Foundation, Inc., 51 Franklin Street,      *
19
 *   Fifth Floor, Boston, MA  02110-1301, USA                              *
20
 *                                                                         *
21
 ***************************************************************************/
22

23

24
#include "PreCompiled.h"
25
#ifndef _PreComp_
26
# include <QApplication>
27
# include <QDebug>
28
# include <QFocusEvent>
29
# include <QFontMetrics>
30
# include <QLineEdit>
31
# include <QRegularExpression>
32
# include <QRegularExpressionMatch>
33
# include <QStyle>
34
# include <QStyleOptionSpinBox>
35
# include <QToolTip>
36
#endif
37

38
#include <sstream>
39

40
#include <App/Application.h>
41
#include <App/Document.h>
42
#include <App/DocumentObject.h>
43
#include <App/ExpressionParser.h>
44
#include <Base/Exception.h>
45
#include <Base/UnitsApi.h>
46
#include <Base/Tools.h>
47

48
#include "QuantitySpinBox.h"
49
#include "QuantitySpinBox_p.h"
50
#include "Command.h"
51
#include "DlgExpressionInput.h"
52
#include "Tools.h"
53

54

55
using namespace Gui;
56
using namespace App;
57
using namespace Base;
58

59
namespace Gui {
60

61
class QuantitySpinBoxPrivate
62
{
63
public:
64
    QuantitySpinBoxPrivate(QuantitySpinBox *q) :
65
      validInput(true),
66
      pendingEmit(false),
67
      checkRangeInExpression(false),
68
      unitValue(0),
69
      maximum(DOUBLE_MAX),
70
      minimum(-DOUBLE_MAX),
71
      singleStep(1.0),
72
      q_ptr(q)
73
    {
74
    }
75
    ~QuantitySpinBoxPrivate() = default;
76

77
    QString stripped(const QString &t, int *pos) const
78
    {
79
        QString text = t;
80
        const int s = text.size();
81
        text = text.trimmed();
82
        if (pos)
83
            (*pos) -= (s - text.size());
84
        return text;
85
    }
86

87
    bool validate(QString& input, Base::Quantity& result, const App::ObjectIdentifier& path) const
88
    {
89
        Q_Q(const QuantitySpinBox);
90

91
        // Do not accept empty strings because the parser will consider
92
        // " unit" as "1 unit" which is not the desired behaviour (see #0004104)
93
        if (input.isEmpty())
94
            return false;
95

96
        bool success = false;
97
        QString tmp = input;
98

99
        auto validateInput = [&](QString& tmp) -> QValidator::State {
100
            QValidator::State state;
101
            Base::Quantity res = validateAndInterpret(tmp, state, path);
102
            res.setFormat(quantity.getFormat());
103
            if (state == QValidator::Acceptable) {
104
                success = true;
105
                result = res;
106
                input = tmp;
107
            }
108
            return state;
109
        };
110

111
        QValidator::State state = validateInput(tmp);
112
        if (state == QValidator::Intermediate && q->hasExpression()) {
113
            // Accept the expression as it is but try to add the right unit string
114
            success = true;
115

116
            Base::Quantity quantity;
117
            double value;
118
            if (parseString(input, quantity, value, path)) {
119
                quantity.setUnit(unit);
120
                result = quantity;
121

122
                // Now translate the quantity into its string representation using the user-defined unit system
123
                input = Base::UnitsApi::schemaTranslate(result);
124
            }
125
        }
126

127
        return success;
128
    }
129
    bool parseString(const QString& str, Base::Quantity& result, double& value, const App::ObjectIdentifier& path) const
130
    {
131
        App::ObjectIdentifier pathtmp = path;
132
        try {
133
            QString copy = str;
134
            copy.remove(locale.groupSeparator());
135

136
            //Expression parser
137
            std::shared_ptr<Expression> expr(ExpressionParser::parse(path.getDocumentObject(), copy.toUtf8().constData()));
138
            if (expr) {
139

140
                std::unique_ptr<Expression> res(expr->eval());
141
                NumberExpression * n = Base::freecad_dynamic_cast<NumberExpression>(res.get());
142
                if (n){
143
                    result = n->getQuantity();
144
                    value = result.getValue();
145
                    return true;
146
                }
147
            }
148
        }
149
        catch (Base::Exception&) {
150
            return false;
151
        }
152
        return false;
153
    }
154
    Base::Quantity validateAndInterpret(QString& input, QValidator::State& state, const App::ObjectIdentifier& path) const
155
    {
156
        Base::Quantity res;
157
        const double max = this->maximum;
158
        const double min = this->minimum;
159

160
        QString copy = input;
161
        double value = min;
162
        bool ok = false;
163

164
        QChar plus = QLatin1Char('+'), minus = QLatin1Char('-');
165

166
        if (locale.negativeSign() != minus)
167
            copy.replace(locale.negativeSign(), minus);
168
        if (locale.positiveSign() != plus)
169
            copy.replace(locale.positiveSign(), plus);
170

171
        QString reverseUnitStr = unitStr;
172
        std::reverse(reverseUnitStr.begin(), reverseUnitStr.end());
173

174
        //Prep for expression parser
175
        //This regex matches chunks between +,-,$,^ accounting for matching parenthesis.
176
        QRegularExpression chunkRe(QString::fromUtf8("(?<=^|[\\+\\-])((\\((?>[^()]|(?2))*\\))|[^\\+\\-\n])*(?=$|[\\+\\-])"));
177
        QRegularExpressionMatchIterator expressionChunk = chunkRe.globalMatch(copy);
178
        unsigned int lengthOffset = 0;
179
        while (expressionChunk.hasNext()) {
180
            QRegularExpressionMatch matchChunk = expressionChunk.next();
181
            QString origionalChunk = matchChunk.captured(0);
182
            QString copyChunk = origionalChunk;
183
            std::reverse(copyChunk.begin(), copyChunk.end());
184

185
            //Reused regex patterns
186
            static const std::string regexUnits = "sAV|VC|lim|nim|im|hpm|[mf]?bl|°|ged|dar|nog|″|′|rroT[uµm]?|K[uµm]?|A[mkM]?|F[pnuµm]?|C|S[uµmkM]?|zH[kMGT]?|H[nuµm]?|mhO[kM]?|J[mk]?|Ve[kM]?|V[mk]?|hWk|sW|lack?|N[mkM]?|g[uµmk]?|lm?|(?<=\\b|[^a-zA-Z])m[nuµmcdk]?|uoht|ni|\"|'|dy|dc|bW|T|t|zo|ts|twc|Wk?|aP[kMG]?|is[pk]|h|G|M|tfc|tfqs|tf|s";
187
            static const std::string regexUnitlessFunctions = "soca|nisa|2nata|nata|hsoc|hnis|hnat|soc|nat|nis|pxe|gol|01gol";
188
            static const std::string regexConstants = "e|ip|lomm|lom";
189
            static const std::string regexNumber = "\\d+\\s*\\.?\\s*\\d*|\\.\\s*\\d+";
190

191
            // If expression does not contain /*() or ^, this regex will not find anything
192
            if (copy.contains(QLatin1Char('/')) || copy.contains(QLatin1Char('*')) || copy.contains(QLatin1Char('(')) || copy.contains(QLatin1Char(')')) || copy.contains(QLatin1Char('^'))){
193
                //Find units and replace 1/2mm -> 1/2*(1mm), 1^2mm -> 1^2*(1mm)
194
                QRegularExpression fixUnits(QString::fromStdString("("+regexUnits+")(\\s*\\)|(?:\\*|(?:\\)(?:(?:\\s*(?:"+regexConstants+"|\\)(?:[^()]|(?R))*\\((?:"+regexUnitlessFunctions+")|"+regexNumber+"))|(?R))*\\(|(?:\\s*(?:"+regexConstants+"|\\)(?:[^()]|(?R))*\\((?:"+regexUnitlessFunctions+")|"+regexNumber+"))))+(?:[\\/\\^]|(.*$))(?!("+regexUnits+")))"));
195
                QRegularExpressionMatch fixUnitsMatch = fixUnits.match(copyChunk);
196

197
                //3rd capture group being filled indicates regex bailed out; no match.
198
                if (fixUnitsMatch.lastCapturedIndex() == 2 || (fixUnitsMatch.lastCapturedIndex() == 3 && fixUnitsMatch.captured(3).isEmpty())){
199
                    QString matchUnits = fixUnitsMatch.captured(1);
200
                    QString matchNumbers = fixUnitsMatch.captured(2);
201
                    copyChunk.replace(matchUnits+matchNumbers, QString::fromUtf8(")")+matchUnits+QString::fromUtf8("1(*")+matchNumbers);
202
                }
203
            }
204

205
            //Add default units to string if none are present
206
            if (!copyChunk.contains(reverseUnitStr)){ // Fast check
207
                QRegularExpression unitsRe(QString::fromStdString("(?<=\\b|[^a-zA-Z])("+regexUnits+")(?=\\b|[^a-zA-Z])|°|″|′|\"|'|\\p{L}\\.\\p{L}|\\[\\p{L}"));
208

209
                QRegularExpressionMatch match = unitsRe.match(copyChunk);
210
                if (!match.hasMatch() && !copyChunk.isEmpty()) //If no units are found, use default units
211
                    copyChunk.prepend(QString::fromUtf8(")")+reverseUnitStr+QString::fromUtf8("1(*")); // Add units to the end of chunk *(1unit)
212
            }
213

214
            std::reverse(copyChunk.begin(), copyChunk.end());
215

216
            copy.replace(matchChunk.capturedStart() + lengthOffset,
217
                    matchChunk.capturedEnd() - matchChunk.capturedStart(), copyChunk);
218
            lengthOffset += copyChunk.length() - origionalChunk.length();
219
        }
220

221
        ok = parseString(copy, res, value, path);
222

223
        // If result does not have unit: add default unit
224
        if (res.getUnit().isEmpty()){
225
            res.setUnit(unit);
226
        }
227

228
        if (!ok) {
229
            // input may not be finished
230
            state = QValidator::Intermediate;
231
        }
232
        else if (value >= min && value <= max) {
233
                state = QValidator::Acceptable;
234
        }
235
        else if (max == min) { // when max and min is the same the only non-Invalid input is max (or min)
236
            state = QValidator::Invalid;
237
        }
238
        else {
239
            if ((value >= 0 && value > max) || (value < 0 && value < min)) {
240
                state = QValidator::Invalid;
241
            }
242
            else {
243
                state = QValidator::Intermediate;
244
            }
245
        }
246
        if (state != QValidator::Acceptable) {
247
            res.setValue(max > 0 ? min : max);
248
        }
249

250
        return res;
251
    }
252

253
    QLocale locale;
254
    bool validInput;
255
    bool pendingEmit;
256
    bool checkRangeInExpression;
257
    QString validStr;
258
    Base::Quantity quantity;
259
    Base::Quantity cached;
260
    Base::Unit unit;
261
    double unitValue;
262
    QString unitStr;
263
    double maximum;
264
    double minimum;
265
    double singleStep;
266
    QuantitySpinBox *q_ptr;
267
    std::unique_ptr<Base::UnitsSchema> scheme;
268
    Q_DECLARE_PUBLIC(QuantitySpinBox)
269
};
270
}
271

272
QuantitySpinBox::QuantitySpinBox(QWidget *parent)
273
    : QAbstractSpinBox(parent),
274
      ExpressionSpinBox(this),
275
      d_ptr(new QuantitySpinBoxPrivate(this))
276
{
277
    d_ptr->locale = locale();
278
    this->setContextMenuPolicy(Qt::DefaultContextMenu);
279
    connect(lineEdit(), &QLineEdit::textChanged,
280
            this, &QuantitySpinBox::userInput);
281
    connect(this, &QuantitySpinBox::editingFinished,
282
            this, [&]{
283
        this->handlePendingEmit(true);
284
    });
285

286
    // When a style sheet is set the text margins for top/bottom must be set to avoid to squash the widget
287
#ifndef Q_OS_MAC
288
    lineEdit()->setTextMargins(0, 2, 0, 2);
289
#else
290
    // https://forum.freecad.org/viewtopic.php?f=8&t=50615
291
    lineEdit()->setTextMargins(0, 2, 0, 0);
292
#endif
293
}
294

295
QuantitySpinBox::~QuantitySpinBox() = default;
296

297
void QuantitySpinBox::bind(const App::ObjectIdentifier &_path)
298
{
299
    ExpressionSpinBox::bind(_path);
300
}
301

302
void QuantitySpinBox::showIcon()
303
{
304
    iconLabel->show();
305
}
306

307
QString QuantitySpinBox::boundToName() const
308
{
309
    if (isBound()) {
310
        std::string path = getPath().toString();
311
        return QString::fromStdString(path);
312
    }
313
    return {};
314
}
315

316
/**
317
 * @brief Create an object identifier by name.
318
 *
319
 * An identifier is written as document#documentobject.property.subproperty1...subpropertyN
320
 * document# may be dropped, in this case the active document is used.
321
 */
322
void QuantitySpinBox::setBoundToByName(const QString &name)
323
{
324
    try {
325
        // get document
326
        App::Document *doc = App::GetApplication().getActiveDocument();
327
        QStringList list = name.split(QLatin1Char('#'));
328
        if (list.size() > 1) {
329
            doc = App::GetApplication().getDocument(list.front().toLatin1());
330
            list.pop_front();
331
        }
332

333
        if (!doc) {
334
            qDebug() << "No such document";
335
            return;
336
        }
337

338
        // first element is assumed to be the document name
339
        list = list.front().split(QLatin1Char('.'));
340

341
        // get object
342
        App::DocumentObject* obj = doc->getObject(list.front().toLatin1());
343
        if (!obj) {
344
            qDebug() << "No object " << list.front() << " in document";
345
            return;
346
        }
347
        list.pop_front();
348

349
        // the rest of the list defines the property and eventually subproperties
350
        App::ObjectIdentifier path(obj);
351
        path.setDocumentName(std::string(doc->getName()), true);
352
        path.setDocumentObjectName(std::string(obj->getNameInDocument()), true);
353

354
        for (const auto & it : list) {
355
            path << App::ObjectIdentifier::Component::SimpleComponent(it.toLatin1().constData());
356
        }
357

358
        if (path.getProperty())
359
            bind(path);
360
    }
361
    catch (const Base::Exception& e) {
362
        qDebug() << e.what();
363
    }
364
}
365

366
QString Gui::QuantitySpinBox::expressionText() const
367
{
368
    try {
369
        if (hasExpression()) {
370
            return QString::fromStdString(getExpressionString());
371
        }
372
    }
373
    catch (const Base::Exception& e) {
374
        qDebug() << e.what();
375
    }
376
    return {};
377
}
378

379
void QuantitySpinBox::evaluateExpression()
380
{
381
    if (isBound() && getExpression()) {
382
        showValidExpression(Number::SetIfNumber);
383
    }
384
}
385

386
void Gui::QuantitySpinBox::setNumberExpression(App::NumberExpression* expr)
387
{
388
    updateEdit(getUserString(expr->getQuantity()));
389
    handlePendingEmit();
390
}
391

392
bool QuantitySpinBox::apply(const std::string & propName)
393
{
394
    if (!ExpressionBinding::apply(propName)) {
395
        double dValue = value().getValue();
396
        return assignToProperty(propName, dValue);
397
    }
398

399
    return false;
400
}
401

402
void QuantitySpinBox::resizeEvent(QResizeEvent * event)
403
{
404
    QAbstractSpinBox::resizeEvent(event);
405
    resizeWidget();
406
}
407

408
void Gui::QuantitySpinBox::keyPressEvent(QKeyEvent *event)
409
{
410
    if (!handleKeyEvent(event->text()))
411
        QAbstractSpinBox::keyPressEvent(event);
412
}
413

414
void Gui::QuantitySpinBox::paintEvent(QPaintEvent*)
415
{
416
    QStyleOptionSpinBox opt;
417
    initStyleOption(&opt);
418
    drawControl(opt);
419
}
420

421
void QuantitySpinBox::updateText(const Quantity &quant)
422
{
423
    Q_D(QuantitySpinBox);
424

425
    double dFactor;
426
    QString txt = getUserString(quant, dFactor, d->unitStr);
427
    d->unitValue = quant.getValue()/dFactor;
428
    updateEdit(txt);
429
    handlePendingEmit();
430
}
431

432
void QuantitySpinBox::updateEdit(const QString& text)
433
{
434
    Q_D(QuantitySpinBox);
435

436
    QLineEdit* edit = lineEdit();
437

438
    bool empty = edit->text().isEmpty();
439
    int cursor = edit->cursorPosition();
440
    int selsize = edit->selectedText().size();
441

442
    edit->setText(text);
443

444
    cursor = qBound(0, cursor, edit->displayText().size() - d->unitStr.size());
445
    if (selsize > 0) {
446
        edit->setSelection(0, cursor);
447
    }
448
    else {
449
        edit->setCursorPosition(empty ? 0 : cursor);
450
    }
451
}
452

453
void QuantitySpinBox::validateInput()
454
{
455
    Q_D(QuantitySpinBox);
456

457
    QValidator::State state;
458
    QString text = lineEdit()->text();
459
    const App::ObjectIdentifier & path = getPath();
460
    d->validateAndInterpret(text, state, path);
461
    if (state != QValidator::Acceptable) {
462
        updateEdit(d->validStr);
463
    }
464

465
    handlePendingEmit();
466
}
467

468
Base::Quantity QuantitySpinBox::value() const
469
{
470
    Q_D(const QuantitySpinBox);
471
    return d->quantity;
472
}
473

474
double QuantitySpinBox::rawValue() const
475
{
476
    Q_D(const QuantitySpinBox);
477
    return d->quantity.getValue();
478
}
479

480
void QuantitySpinBox::setValue(const Base::Quantity& value)
481
{
482
    Q_D(QuantitySpinBox);
483
    d->quantity = value;
484
    // check limits
485
    if (d->quantity.getValue() > d->maximum)
486
        d->quantity.setValue(d->maximum);
487
    if (d->quantity.getValue() < d->minimum)
488
        d->quantity.setValue(d->minimum);
489

490
    d->unit = value.getUnit();
491

492
    updateText(value);
493
}
494

495
void QuantitySpinBox::setValue(double value)
496
{
497
    Q_D(QuantitySpinBox);
498

499
    Base::QuantityFormat currentformat = d->quantity.getFormat();
500
    auto quantity = Base::Quantity(value, d->unit);
501
    quantity.setFormat(currentformat);
502

503
    setValue(quantity);
504
}
505

506
bool QuantitySpinBox::hasValidInput() const
507
{
508
    Q_D(const QuantitySpinBox);
509
    return d->validInput;
510
}
511

512
// Gets called after call of 'validateAndInterpret'
513
void QuantitySpinBox::userInput(const QString & text)
514
{
515
    Q_D(QuantitySpinBox);
516

517
    d->pendingEmit = true;
518

519
    QString tmp = text;
520
    Base::Quantity res;
521
    const App::ObjectIdentifier & path = getPath();
522
    if (d->validate(tmp, res, path)) {
523
        d->validStr = tmp;
524
        d->validInput = true;
525
    }
526
    else {
527
        d->validInput = false;
528
        return;
529
    }
530

531
    if (keyboardTracking()) {
532
        d->cached = res;
533
        handlePendingEmit(false);
534
    }
535
    else {
536
        d->cached = res;
537
    }
538
}
539

540
void QuantitySpinBox::openFormulaDialog()
541
{
542
    Q_ASSERT(isBound());
543

544
    Q_D(const QuantitySpinBox);
545
    auto box = new Gui::Dialog::DlgExpressionInput(getPath(), getExpression(), d->unit, this);
546
    if (d->checkRangeInExpression) {
547
        box->setRange(d->minimum, d->maximum);
548
    }
549
    QObject::connect(box, &Gui::Dialog::DlgExpressionInput::finished, [this, box]() {
550
        if (box->result() == QDialog::Accepted)
551
            setExpression(box->getExpression());
552
        else if (box->discardedFormula())
553
            setExpression(std::shared_ptr<Expression>());
554

555
        box->deleteLater();
556
        Q_EMIT showFormulaDialog(false);
557
    });
558
    box->show();
559

560
    QPoint pos = mapToGlobal(QPoint(0,0));
561
    box->move(pos-box->expressionPosition());
562
    box->setExpressionInputSize(width(), height());
563

564
    Q_EMIT showFormulaDialog(true);
565
}
566

567
void QuantitySpinBox::handlePendingEmit(bool updateUnit /* = true */)
568
{
569
    updateFromCache(true, updateUnit);
570
}
571

572
void QuantitySpinBox::updateFromCache(bool notify, bool updateUnit /* = true */)
573
{
574
    Q_D(QuantitySpinBox);
575
    if (d->pendingEmit) {
576
        double factor;
577
        const Base::Quantity& res = d->cached;
578
        auto tmpUnit(d->unitStr);
579
        QString text = getUserString(res, factor, updateUnit ? d->unitStr : tmpUnit);
580
        d->unitValue = res.getValue() / factor;
581
        d->quantity = res;
582

583
        // signaling
584
        if (notify) {
585
            d->pendingEmit = false;
586
            Q_EMIT valueChanged(res);
587
            Q_EMIT valueChanged(res.getValue());
588
            Q_EMIT textChanged(text);
589
        }
590
    }
591
}
592

593
Base::Unit QuantitySpinBox::unit() const
594
{
595
    Q_D(const QuantitySpinBox);
596
    return d->unit;
597
}
598

599
void QuantitySpinBox::setUnit(const Base::Unit &unit)
600
{
601
    Q_D(QuantitySpinBox);
602

603
    d->unit = unit;
604
    d->quantity.setUnit(unit);
605
    updateText(d->quantity);
606
}
607

608
void QuantitySpinBox::setUnitText(const QString& str)
609
{
610
    try {
611
        Base::Quantity quant = Base::Quantity::parse(str);
612
        setUnit(quant.getUnit());
613
    }
614
    catch (const Base::ParserError&) {
615
    }
616
}
617

618
QString QuantitySpinBox::unitText()
619
{
620
    Q_D(QuantitySpinBox);
621
    return d->unitStr;
622
}
623

624
double QuantitySpinBox::singleStep() const
625
{
626
    Q_D(const QuantitySpinBox);
627
    return d->singleStep;
628
}
629

630
void QuantitySpinBox::setSingleStep(double value)
631
{
632
    Q_D(QuantitySpinBox);
633

634
    if (value >= 0) {
635
        d->singleStep = value;
636
    }
637
}
638

639
double QuantitySpinBox::minimum() const
640
{
641
    Q_D(const QuantitySpinBox);
642
    return d->minimum;
643
}
644

645
void QuantitySpinBox::setMinimum(double minimum)
646
{
647
    Q_D(QuantitySpinBox);
648
    d->minimum = minimum;
649
}
650

651
double QuantitySpinBox::maximum() const
652
{
653
    Q_D(const QuantitySpinBox);
654
    return d->maximum;
655
}
656

657
void QuantitySpinBox::setMaximum(double maximum)
658
{
659
    Q_D(QuantitySpinBox);
660
    d->maximum = maximum;
661
}
662

663
void QuantitySpinBox::setRange(double minimum, double maximum)
664
{
665
    Q_D(QuantitySpinBox);
666
    d->minimum = minimum;
667
    d->maximum = maximum;
668
}
669

670
void QuantitySpinBox::checkRangeInExpression(bool on)
671
{
672
    Q_D(QuantitySpinBox);
673
    d->checkRangeInExpression = on;
674
}
675

676
bool QuantitySpinBox::isCheckedRangeInExpresion() const
677
{
678
    Q_D(const QuantitySpinBox);
679
    return d->checkRangeInExpression;
680
}
681

682

683
int QuantitySpinBox::decimals() const
684
{
685
    Q_D(const QuantitySpinBox);
686
    return d->quantity.getFormat().precision;
687
}
688

689
void QuantitySpinBox::setDecimals(int v)
690
{
691
    Q_D(QuantitySpinBox);
692
    Base::QuantityFormat f = d->quantity.getFormat();
693
    f.precision = v;
694
    d->quantity.setFormat(f);
695
    updateText(d->quantity);
696
}
697

698
void QuantitySpinBox::setSchema(const Base::UnitSystem& s)
699
{
700
    Q_D(QuantitySpinBox);
701
    d->scheme = Base::UnitsApi::createSchema(s);
702
    updateText(d->quantity);
703
}
704

705
void QuantitySpinBox::clearSchema()
706
{
707
    Q_D(QuantitySpinBox);
708
    d->scheme = nullptr;
709
    updateText(d->quantity);
710
}
711

712
QString QuantitySpinBox::getUserString(const Base::Quantity& val, double& factor, QString& unitString) const
713
{
714
    Q_D(const QuantitySpinBox);
715
    if (d->scheme) {
716
        return val.getUserString(d->scheme.get(), factor, unitString);
717
    }
718
    else {
719
        return val.getUserString(factor, unitString);
720
    }
721
}
722

723
QString QuantitySpinBox::getUserString(const Base::Quantity& val) const
724
{
725
    Q_D(const QuantitySpinBox);
726
    if (d->scheme) {
727
        double factor;
728
        QString unitString;
729
        return val.getUserString(d->scheme.get(), factor, unitString);
730
    }
731
    else {
732
        return val.getUserString();
733
    }
734
}
735

736
void QuantitySpinBox::setExpression(std::shared_ptr<Expression> expr)
737
{
738
    ExpressionSpinBox::setExpression(expr);
739
}
740

741
QAbstractSpinBox::StepEnabled QuantitySpinBox::stepEnabled() const
742
{
743
    Q_D(const QuantitySpinBox);
744
    if (isReadOnly()/* || !d->validInput*/)
745
        return StepNone;
746
    if (wrapping())
747
        return StepEnabled(StepUpEnabled | StepDownEnabled);
748
    StepEnabled ret = StepNone;
749
    if (d->quantity.getValue() < d->maximum) {
750
        ret |= StepUpEnabled;
751
    }
752
    if (d->quantity.getValue() > d->minimum) {
753
        ret |= StepDownEnabled;
754
    }
755
    return ret;
756
}
757

758
void QuantitySpinBox::stepBy(int steps)
759
{
760
    Q_D(QuantitySpinBox);
761
    updateFromCache(false);
762

763
    double step = d->singleStep * steps;
764
    double val = d->unitValue + step;
765
    if (val > d->maximum)
766
        val = d->maximum;
767
    else if (val < d->minimum)
768
        val = d->minimum;
769

770
    Quantity quant(val, d->unitStr);
771
    updateText(quant);
772
    updateFromCache(true);
773
    update();
774
    selectNumber();
775
}
776

777
QSize QuantitySpinBox::sizeForText(const QString& txt) const
778
{
779
    const QFontMetrics fm(fontMetrics());
780
    int h = lineEdit()->sizeHint().height();
781
    int w = QtTools::horizontalAdvance(fm, txt);
782

783
    w += 2; // cursor blinking space
784
    w += iconHeight;
785

786
    QStyleOptionSpinBox opt;
787
    initStyleOption(&opt);
788
    QSize hint(w, h);
789
    QSize size = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this);
790
    return size;
791
}
792

793
QSize QuantitySpinBox::sizeHint() const
794
{
795
    Q_D(const QuantitySpinBox);
796
    ensurePolished();
797

798
    const QFontMetrics fm(fontMetrics());
799
    int h = lineEdit()->sizeHint().height();
800
    int w = 0;
801

802
    QString s;
803
    QString fixedContent = QLatin1String(" ");
804

805
    Base::Quantity q(d->quantity);
806
    q.setValue(d->maximum);
807
    s = textFromValue(q);
808
    s.truncate(18);
809
    s += fixedContent;
810
    w = qMax(w, QtTools::horizontalAdvance(fm, s));
811

812
    w += 2; // cursor blinking space
813
    w += iconHeight;
814

815
    QStyleOptionSpinBox opt;
816
    initStyleOption(&opt);
817
    QSize hint(w, h);
818
    QSize size = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this);
819
    return size;
820
}
821

822
QSize QuantitySpinBox::minimumSizeHint() const
823
{
824
    Q_D(const QuantitySpinBox);
825
    ensurePolished();
826

827
    const QFontMetrics fm(fontMetrics());
828
    int h = lineEdit()->minimumSizeHint().height();
829
    int w = 0;
830

831
    QString s;
832
    QString fixedContent = QLatin1String(" ");
833

834
    Base::Quantity q(d->quantity);
835
    q.setValue(d->maximum);
836
    s = textFromValue(q);
837
    s.truncate(18);
838
    s += fixedContent;
839
    w = qMax(w, QtTools::horizontalAdvance(fm, s));
840

841
    w += 2; // cursor blinking space
842
    w += iconHeight;
843

844
    QStyleOptionSpinBox opt;
845
    initStyleOption(&opt);
846
    QSize hint(w, h);
847

848
    QSize size = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this);
849
    return size;
850
}
851

852
void QuantitySpinBox::showEvent(QShowEvent * event)
853
{
854
    Q_D(QuantitySpinBox);
855

856
    QAbstractSpinBox::showEvent(event);
857

858
    bool selected = lineEdit()->hasSelectedText();
859
    updateText(d->quantity);
860
    if (selected)
861
        selectNumber();
862
}
863

864
void QuantitySpinBox::hideEvent(QHideEvent * event)
865
{
866
    handlePendingEmit();
867
    QAbstractSpinBox::hideEvent(event);
868
}
869

870
void QuantitySpinBox::closeEvent(QCloseEvent * event)
871
{
872
    handlePendingEmit();
873
    QAbstractSpinBox::closeEvent(event);
874
}
875

876
bool QuantitySpinBox::event(QEvent * event)
877
{
878
    return QAbstractSpinBox::event(event);
879
}
880

881
void QuantitySpinBox::focusInEvent(QFocusEvent * event)
882
{
883
    bool hasSel = lineEdit()->hasSelectedText();
884
    QAbstractSpinBox::focusInEvent(event);
885

886
    if (event->reason() == Qt::TabFocusReason ||
887
        event->reason() == Qt::BacktabFocusReason  ||
888
        event->reason() == Qt::ShortcutFocusReason) {
889

890
        if (isBound() && getExpression() && lineEdit()->isReadOnly()) {
891
            auto helpEvent = new QHelpEvent(QEvent::ToolTip, QPoint( 0, rect().height() ), mapToGlobal( QPoint( 0, rect().height() ) ));
892
            QApplication::postEvent(this, helpEvent);
893
            lineEdit()->setSelection(0, 0);
894
        }
895
        else {
896
            if (!hasSel)
897
                selectNumber();
898
        }
899
    }
900
}
901

902
void QuantitySpinBox::focusOutEvent(QFocusEvent * event)
903
{
904
    validateInput();
905

906
    QToolTip::hideText();
907
    QAbstractSpinBox::focusOutEvent(event);
908
}
909

910
void QuantitySpinBox::clear()
911
{
912
    QAbstractSpinBox::clear();
913
}
914

915
void QuantitySpinBox::selectNumber()
916
{
917
    QString expr = QString::fromLatin1("^([%1%2]?[0-9\\%3]*)\\%4?([0-9]+(%5[%1%2]?[0-9]+)?)")
918
                   .arg(locale().negativeSign())
919
                   .arg(locale().positiveSign())
920
                   .arg(locale().groupSeparator())
921
                   .arg(locale().decimalPoint())
922
                   .arg(locale().exponential());
923
    auto rmatch = QRegularExpression(expr).match(lineEdit()->text());
924
    if (rmatch.hasMatch()) {
925
        lineEdit()->setSelection(0, rmatch.capturedLength());
926
    }
927
}
928

929
QString QuantitySpinBox::textFromValue(const Base::Quantity& value) const
930
{
931
    double factor;
932
    QString unitStr;
933
    QString str = getUserString(value, factor, unitStr);
934
    if (qAbs(value.getValue()) >= 1000.0) {
935
        str.remove(locale().groupSeparator());
936
    }
937
    return str;
938
}
939

940
Base::Quantity QuantitySpinBox::valueFromText(const QString &text) const
941
{
942
    Q_D(const QuantitySpinBox);
943

944
    QString copy = text;
945
    QValidator::State state = QValidator::Acceptable;
946
    const App::ObjectIdentifier & path = getPath();
947
    Base::Quantity quant = d->validateAndInterpret(copy, state, path);
948
    if (state != QValidator::Acceptable) {
949
        fixup(copy);
950
        quant = d->validateAndInterpret(copy, state, path);
951
    }
952

953
    return quant;
954
}
955

956
QValidator::State QuantitySpinBox::validate(QString &text, int &pos) const
957
{
958
    Q_D(const QuantitySpinBox);
959
    Q_UNUSED(pos)
960

961
    QValidator::State state;
962
    const App::ObjectIdentifier & path = getPath();
963
    d->validateAndInterpret(text, state, path);
964
    return state;
965
}
966

967
void QuantitySpinBox::fixup(QString &input) const
968
{
969
    input.remove(locale().groupSeparator());
970
}
971

972

973
#include "moc_QuantitySpinBox.cpp"
974
#include "moc_QuantitySpinBox_p.cpp"
975

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.