mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-27 11:43:26 -03:00
8711cc0c78
This adds functions for specifing a min/max value for a BitcoinAmountField. These options only affect user input, so it's still possible to use setValue to set values outside of the min/max range. The existing value will not be changed when calling these functions even if it's out of range. The min/max range will be reinforced when the field loses focus. This also adds `SetAllowEmpty` function which specifies if the field is allowed to be left empty by the user. If set to false the field will be set to the minimum allowed value if it's empty when focus is lost.
341 lines
8.9 KiB
C++
341 lines
8.9 KiB
C++
// Copyright (c) 2011-2018 The Bitcoin Core developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#include <qt/bitcoinamountfield.h>
|
|
|
|
#include <qt/bitcoinunits.h>
|
|
#include <qt/guiconstants.h>
|
|
#include <qt/qvaluecombobox.h>
|
|
|
|
#include <QApplication>
|
|
#include <QAbstractSpinBox>
|
|
#include <QHBoxLayout>
|
|
#include <QKeyEvent>
|
|
#include <QLineEdit>
|
|
|
|
/** QSpinBox that uses fixed-point numbers internally and uses our own
|
|
* formatting/parsing functions.
|
|
*/
|
|
class AmountSpinBox: public QAbstractSpinBox
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
explicit AmountSpinBox(QWidget *parent):
|
|
QAbstractSpinBox(parent)
|
|
{
|
|
setAlignment(Qt::AlignRight);
|
|
|
|
connect(lineEdit(), &QLineEdit::textEdited, this, &AmountSpinBox::valueChanged);
|
|
}
|
|
|
|
QValidator::State validate(QString &text, int &pos) const
|
|
{
|
|
if(text.isEmpty())
|
|
return QValidator::Intermediate;
|
|
bool valid = false;
|
|
parse(text, &valid);
|
|
/* Make sure we return Intermediate so that fixup() is called on defocus */
|
|
return valid ? QValidator::Intermediate : QValidator::Invalid;
|
|
}
|
|
|
|
void fixup(QString &input) const
|
|
{
|
|
bool valid;
|
|
CAmount val;
|
|
|
|
if (input.isEmpty() && !m_allow_empty) {
|
|
valid = true;
|
|
val = m_min_amount;
|
|
} else {
|
|
valid = false;
|
|
val = parse(input, &valid);
|
|
}
|
|
|
|
if (valid) {
|
|
val = qBound(m_min_amount, val, m_max_amount);
|
|
input = BitcoinUnits::format(currentUnit, val, false, BitcoinUnits::separatorAlways);
|
|
lineEdit()->setText(input);
|
|
}
|
|
}
|
|
|
|
CAmount value(bool *valid_out=0) const
|
|
{
|
|
return parse(text(), valid_out);
|
|
}
|
|
|
|
void setValue(const CAmount& value)
|
|
{
|
|
lineEdit()->setText(BitcoinUnits::format(currentUnit, value, false, BitcoinUnits::separatorAlways));
|
|
Q_EMIT valueChanged();
|
|
}
|
|
|
|
void SetAllowEmpty(bool allow)
|
|
{
|
|
m_allow_empty = allow;
|
|
}
|
|
|
|
void SetMinValue(const CAmount& value)
|
|
{
|
|
m_min_amount = value;
|
|
}
|
|
|
|
void SetMaxValue(const CAmount& value)
|
|
{
|
|
m_max_amount = value;
|
|
}
|
|
|
|
void stepBy(int steps)
|
|
{
|
|
bool valid = false;
|
|
CAmount val = value(&valid);
|
|
val = val + steps * singleStep;
|
|
val = qBound(m_min_amount, val, m_max_amount);
|
|
setValue(val);
|
|
}
|
|
|
|
void setDisplayUnit(int unit)
|
|
{
|
|
bool valid = false;
|
|
CAmount val = value(&valid);
|
|
|
|
currentUnit = unit;
|
|
|
|
if(valid)
|
|
setValue(val);
|
|
else
|
|
clear();
|
|
}
|
|
|
|
void setSingleStep(const CAmount& step)
|
|
{
|
|
singleStep = step;
|
|
}
|
|
|
|
QSize minimumSizeHint() const
|
|
{
|
|
if(cachedMinimumSizeHint.isEmpty())
|
|
{
|
|
ensurePolished();
|
|
|
|
const QFontMetrics fm(fontMetrics());
|
|
int h = lineEdit()->minimumSizeHint().height();
|
|
int w = fm.width(BitcoinUnits::format(BitcoinUnits::BTC, BitcoinUnits::maxMoney(), false, BitcoinUnits::separatorAlways));
|
|
w += 2; // cursor blinking space
|
|
|
|
QStyleOptionSpinBox opt;
|
|
initStyleOption(&opt);
|
|
QSize hint(w, h);
|
|
QSize extra(35, 6);
|
|
opt.rect.setSize(hint + extra);
|
|
extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &opt,
|
|
QStyle::SC_SpinBoxEditField, this).size();
|
|
// get closer to final result by repeating the calculation
|
|
opt.rect.setSize(hint + extra);
|
|
extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &opt,
|
|
QStyle::SC_SpinBoxEditField, this).size();
|
|
hint += extra;
|
|
hint.setHeight(h);
|
|
|
|
opt.rect = rect();
|
|
|
|
cachedMinimumSizeHint = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this)
|
|
.expandedTo(QApplication::globalStrut());
|
|
}
|
|
return cachedMinimumSizeHint;
|
|
}
|
|
|
|
private:
|
|
int currentUnit{BitcoinUnits::BTC};
|
|
CAmount singleStep{CAmount(100000)}; // satoshis
|
|
mutable QSize cachedMinimumSizeHint;
|
|
bool m_allow_empty{true};
|
|
CAmount m_min_amount{CAmount(0)};
|
|
CAmount m_max_amount{BitcoinUnits::maxMoney()};
|
|
|
|
/**
|
|
* Parse a string into a number of base monetary units and
|
|
* return validity.
|
|
* @note Must return 0 if !valid.
|
|
*/
|
|
CAmount parse(const QString &text, bool *valid_out=0) const
|
|
{
|
|
CAmount val = 0;
|
|
bool valid = BitcoinUnits::parse(currentUnit, text, &val);
|
|
if(valid)
|
|
{
|
|
if(val < 0 || val > BitcoinUnits::maxMoney())
|
|
valid = false;
|
|
}
|
|
if(valid_out)
|
|
*valid_out = valid;
|
|
return valid ? val : 0;
|
|
}
|
|
|
|
protected:
|
|
bool event(QEvent *event)
|
|
{
|
|
if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease)
|
|
{
|
|
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
|
|
if (keyEvent->key() == Qt::Key_Comma)
|
|
{
|
|
// Translate a comma into a period
|
|
QKeyEvent periodKeyEvent(event->type(), Qt::Key_Period, keyEvent->modifiers(), ".", keyEvent->isAutoRepeat(), keyEvent->count());
|
|
return QAbstractSpinBox::event(&periodKeyEvent);
|
|
}
|
|
}
|
|
return QAbstractSpinBox::event(event);
|
|
}
|
|
|
|
StepEnabled stepEnabled() const
|
|
{
|
|
if (isReadOnly()) // Disable steps when AmountSpinBox is read-only
|
|
return StepNone;
|
|
if (text().isEmpty()) // Allow step-up with empty field
|
|
return StepUpEnabled;
|
|
|
|
StepEnabled rv = 0;
|
|
bool valid = false;
|
|
CAmount val = value(&valid);
|
|
if (valid) {
|
|
if (val > m_min_amount)
|
|
rv |= StepDownEnabled;
|
|
if (val < m_max_amount)
|
|
rv |= StepUpEnabled;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
Q_SIGNALS:
|
|
void valueChanged();
|
|
};
|
|
|
|
#include <qt/bitcoinamountfield.moc>
|
|
|
|
BitcoinAmountField::BitcoinAmountField(QWidget *parent) :
|
|
QWidget(parent),
|
|
amount(0)
|
|
{
|
|
amount = new AmountSpinBox(this);
|
|
amount->setLocale(QLocale::c());
|
|
amount->installEventFilter(this);
|
|
amount->setMaximumWidth(240);
|
|
|
|
QHBoxLayout *layout = new QHBoxLayout(this);
|
|
layout->addWidget(amount);
|
|
unit = new QValueComboBox(this);
|
|
unit->setModel(new BitcoinUnits(this));
|
|
layout->addWidget(unit);
|
|
layout->addStretch(1);
|
|
layout->setContentsMargins(0,0,0,0);
|
|
|
|
setLayout(layout);
|
|
|
|
setFocusPolicy(Qt::TabFocus);
|
|
setFocusProxy(amount);
|
|
|
|
// If one if the widgets changes, the combined content changes as well
|
|
connect(amount, &AmountSpinBox::valueChanged, this, &BitcoinAmountField::valueChanged);
|
|
connect(unit, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &BitcoinAmountField::unitChanged);
|
|
|
|
// Set default based on configuration
|
|
unitChanged(unit->currentIndex());
|
|
}
|
|
|
|
void BitcoinAmountField::clear()
|
|
{
|
|
amount->clear();
|
|
unit->setCurrentIndex(0);
|
|
}
|
|
|
|
void BitcoinAmountField::setEnabled(bool fEnabled)
|
|
{
|
|
amount->setEnabled(fEnabled);
|
|
unit->setEnabled(fEnabled);
|
|
}
|
|
|
|
bool BitcoinAmountField::validate()
|
|
{
|
|
bool valid = false;
|
|
value(&valid);
|
|
setValid(valid);
|
|
return valid;
|
|
}
|
|
|
|
void BitcoinAmountField::setValid(bool valid)
|
|
{
|
|
if (valid)
|
|
amount->setStyleSheet("");
|
|
else
|
|
amount->setStyleSheet(STYLE_INVALID);
|
|
}
|
|
|
|
bool BitcoinAmountField::eventFilter(QObject *object, QEvent *event)
|
|
{
|
|
if (event->type() == QEvent::FocusIn)
|
|
{
|
|
// Clear invalid flag on focus
|
|
setValid(true);
|
|
}
|
|
return QWidget::eventFilter(object, event);
|
|
}
|
|
|
|
QWidget *BitcoinAmountField::setupTabChain(QWidget *prev)
|
|
{
|
|
QWidget::setTabOrder(prev, amount);
|
|
QWidget::setTabOrder(amount, unit);
|
|
return unit;
|
|
}
|
|
|
|
CAmount BitcoinAmountField::value(bool *valid_out) const
|
|
{
|
|
return amount->value(valid_out);
|
|
}
|
|
|
|
void BitcoinAmountField::setValue(const CAmount& value)
|
|
{
|
|
amount->setValue(value);
|
|
}
|
|
|
|
void BitcoinAmountField::SetAllowEmpty(bool allow)
|
|
{
|
|
amount->SetAllowEmpty(allow);
|
|
}
|
|
|
|
void BitcoinAmountField::SetMinValue(const CAmount& value)
|
|
{
|
|
amount->SetMinValue(value);
|
|
}
|
|
|
|
void BitcoinAmountField::SetMaxValue(const CAmount& value)
|
|
{
|
|
amount->SetMaxValue(value);
|
|
}
|
|
|
|
void BitcoinAmountField::setReadOnly(bool fReadOnly)
|
|
{
|
|
amount->setReadOnly(fReadOnly);
|
|
}
|
|
|
|
void BitcoinAmountField::unitChanged(int idx)
|
|
{
|
|
// Use description tooltip for current unit for the combobox
|
|
unit->setToolTip(unit->itemData(idx, Qt::ToolTipRole).toString());
|
|
|
|
// Determine new unit ID
|
|
int newUnit = unit->itemData(idx, BitcoinUnits::UnitRole).toInt();
|
|
|
|
amount->setDisplayUnit(newUnit);
|
|
}
|
|
|
|
void BitcoinAmountField::setDisplayUnit(int newUnit)
|
|
{
|
|
unit->setValue(newUnit);
|
|
}
|
|
|
|
void BitcoinAmountField::setSingleStep(const CAmount& step)
|
|
{
|
|
amount->setSingleStep(step);
|
|
}
|