Browse Source

Merge pull request #4556

29eaa31 ui: Make sure sendcoinsentry signals only connected once (Wladimir J. van der Laan)
2a05101 qt: Remove unused functions from BitcoinUnits (Wladimir J. van der Laan)
91cce17 qt: Use fixed-point arithmetic in amount spinbox (Wladimir J. van der Laan)
0.10
Wladimir J. van der Laan 10 years ago
parent
commit
7bf8ab9dd3
No known key found for this signature in database
GPG Key ID: 74810B012346C9A6
  1. 1
      src/Makefile.qt.include
  2. 266
      src/qt/bitcoinamountfield.cpp
  3. 14
      src/qt/bitcoinamountfield.h
  4. 29
      src/qt/bitcoinunits.cpp
  5. 7
      src/qt/bitcoinunits.h
  6. 18
      src/qt/sendcoinsentry.cpp

1
src/Makefile.qt.include

@ -145,6 +145,7 @@ BITCOIN_MM = \ @@ -145,6 +145,7 @@ BITCOIN_MM = \
QT_MOC = \
qt/bitcoin.moc \
qt/bitcoinamountfield.moc \
qt/intro.moc \
qt/overviewpage.moc \
qt/rpcconsole.moc

266
src/qt/bitcoinamountfield.cpp

@ -9,63 +9,185 @@ @@ -9,63 +9,185 @@
#include "qvaluecombobox.h"
#include <QApplication>
#include <QDoubleSpinBox>
#include <QAbstractSpinBox>
#include <QHBoxLayout>
#include <QKeyEvent>
#include <qmath.h> // for qPow()
#include <QLineEdit>
// QDoubleSpinBox that shows SI-style thin space thousands separators
class AmountSpinBox: public QDoubleSpinBox
/** 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):
QDoubleSpinBox(parent)
QAbstractSpinBox(parent),
currentUnit(BitcoinUnits::BTC),
singleStep(100000) // satoshis
{
setAlignment(Qt::AlignRight);
connect(lineEdit(), SIGNAL(textEdited(QString)), this, SIGNAL(valueChanged()));
}
QString textFromValue(double value) const
QValidator::State validate(QString &text, int &pos) const
{
QStringList parts = QDoubleSpinBox::textFromValue(value).split(".");
QString quotient_str = parts[0];
QString remainder_str;
if(parts.size() > 1)
remainder_str = parts[1];
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;
}
// Code duplication between here and BitcoinUnits::format
// TODO: Figure out how to share this code
QChar thin_sp(THIN_SP_CP);
int q_size = quotient_str.size();
if (q_size > 4)
for (int i = 3; i < q_size; i += 3)
quotient_str.insert(q_size - i, thin_sp);
void fixup(QString &input) const
{
bool valid = false;
qint64 val = parse(input, &valid);
if(valid)
{
input = BitcoinUnits::format(currentUnit, val, false, BitcoinUnits::separatorAlways);
lineEdit()->setText(input);
}
}
int r_size = remainder_str.size();
if (r_size > 4)
for (int i = 3, adj = 0; i < r_size; i += 3, adj++)
remainder_str.insert(i + adj, thin_sp);
qint64 value(bool *valid_out=0) const
{
return parse(text(), valid_out);
}
if(remainder_str.isEmpty())
return quotient_str;
void setValue(qint64 value)
{
lineEdit()->setText(BitcoinUnits::format(currentUnit, value, false, BitcoinUnits::separatorAlways));
emit valueChanged();
}
void stepBy(int steps)
{
bool valid = false;
qint64 val = value(&valid);
val = val + steps * singleStep;
val = qMin(qMax(val, Q_INT64_C(0)), BitcoinUnits::maxMoney());
setValue(val);
}
StepEnabled stepEnabled() const
{
StepEnabled rv = 0;
if(text().isEmpty()) // Allow step-up with empty field
return StepUpEnabled;
bool valid = false;
qint64 val = value(&valid);
if(valid)
{
if(val > 0)
rv |= StepDownEnabled;
if(val < BitcoinUnits::maxMoney())
rv |= StepUpEnabled;
}
return rv;
}
void setDisplayUnit(int unit)
{
bool valid = false;
qint64 val = value(&valid);
currentUnit = unit;
if(valid)
setValue(val);
else
return quotient_str + QString(".") + remainder_str;
clear();
}
void setSingleStep(qint64 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;
opt.rect = rect();
cachedMinimumSizeHint = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this)
.expandedTo(QApplication::globalStrut());
}
return cachedMinimumSizeHint;
}
QValidator::State validate (QString &text, int &pos) const
private:
int currentUnit;
qint64 singleStep;
mutable QSize cachedMinimumSizeHint;
/**
* Parse a string into a number of base monetary units and
* return validity.
* @note Must return 0 if !valid.
*/
qint64 parse(const QString &text, bool *valid_out=0) const
{
qint64 val = 0;
bool valid = BitcoinUnits::parse(currentUnit, text, &val);
if(valid)
{
QString s(BitcoinUnits::removeSpaces(text));
return QDoubleSpinBox::validate(s, pos);
if(val < 0 || val > BitcoinUnits::maxMoney())
valid = false;
}
if(valid_out)
*valid_out = valid;
return valid ? val : 0;
}
double valueFromText(const QString& text) const
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)
{
return QDoubleSpinBox::valueFromText(BitcoinUnits::removeSpaces(text));
// 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);
}
signals:
void valueChanged();
};
#include "bitcoinamountfield.moc"
BitcoinAmountField::BitcoinAmountField(QWidget *parent) :
QWidget(parent),
amount(0),
currentUnit(-1)
amount(0)
{
nSingleStep = 100000; // satoshis
amount = new AmountSpinBox(this);
amount->setLocale(QLocale::c());
amount->installEventFilter(this);
@ -85,21 +207,13 @@ BitcoinAmountField::BitcoinAmountField(QWidget *parent) : @@ -85,21 +207,13 @@ BitcoinAmountField::BitcoinAmountField(QWidget *parent) :
setFocusProxy(amount);
// If one if the widgets changes, the combined content changes as well
connect(amount, SIGNAL(valueChanged(QString)), this, SIGNAL(textChanged()));
connect(amount, SIGNAL(valueChanged()), this, SIGNAL(valueChanged()));
connect(unit, SIGNAL(currentIndexChanged(int)), this, SLOT(unitChanged(int)));
// Set default based on configuration
unitChanged(unit->currentIndex());
}
void BitcoinAmountField::setText(const QString &text)
{
if (text.isEmpty())
amount->clear();
else
amount->setValue(BitcoinUnits::removeSpaces(text).toDouble());
}
void BitcoinAmountField::clear()
{
amount->clear();
@ -108,16 +222,9 @@ void BitcoinAmountField::clear() @@ -108,16 +222,9 @@ void BitcoinAmountField::clear()
bool BitcoinAmountField::validate()
{
bool valid = true;
if (amount->value() == 0.0)
valid = false;
else if (!BitcoinUnits::parse(currentUnit, text(), 0))
valid = false;
else if (amount->value() > BitcoinUnits::maxAmount(currentUnit))
valid = false;
bool valid = false;
value(&valid);
setValid(valid);
return valid;
}
@ -129,14 +236,6 @@ void BitcoinAmountField::setValid(bool valid) @@ -129,14 +236,6 @@ void BitcoinAmountField::setValid(bool valid)
amount->setStyleSheet(STYLE_INVALID);
}
QString BitcoinAmountField::text() const
{
if (amount->text().isEmpty())
return QString();
else
return amount->text();
}
bool BitcoinAmountField::eventFilter(QObject *object, QEvent *event)
{
if (event->type() == QEvent::FocusIn)
@ -144,17 +243,6 @@ bool BitcoinAmountField::eventFilter(QObject *object, QEvent *event) @@ -144,17 +243,6 @@ bool BitcoinAmountField::eventFilter(QObject *object, QEvent *event)
// Clear invalid flag on focus
setValid(true);
}
else 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());
QApplication::sendEvent(object, &periodKeyEvent);
return true;
}
}
return QWidget::eventFilter(object, event);
}
@ -167,18 +255,12 @@ QWidget *BitcoinAmountField::setupTabChain(QWidget *prev) @@ -167,18 +255,12 @@ QWidget *BitcoinAmountField::setupTabChain(QWidget *prev)
qint64 BitcoinAmountField::value(bool *valid_out) const
{
qint64 val_out = 0;
bool valid = BitcoinUnits::parse(currentUnit, text(), &val_out);
if (valid_out)
{
*valid_out = valid;
}
return val_out;
return amount->value(valid_out);
}
void BitcoinAmountField::setValue(qint64 value)
{
setText(BitcoinUnits::format(currentUnit, value));
amount->setValue(value);
}
void BitcoinAmountField::setReadOnly(bool fReadOnly)
@ -195,28 +277,7 @@ void BitcoinAmountField::unitChanged(int idx) @@ -195,28 +277,7 @@ void BitcoinAmountField::unitChanged(int idx)
// Determine new unit ID
int newUnit = unit->itemData(idx, BitcoinUnits::UnitRole).toInt();
// Parse current value and convert to new unit
bool valid = false;
qint64 currentValue = value(&valid);
currentUnit = newUnit;
// Set max length after retrieving the value, to prevent truncation
amount->setDecimals(BitcoinUnits::decimals(currentUnit));
amount->setMaximum(qPow(10, BitcoinUnits::amountDigits(currentUnit)) - qPow(10, -amount->decimals()));
amount->setSingleStep((double)nSingleStep / (double)BitcoinUnits::factor(currentUnit));
if (valid)
{
// If value was valid, re-place it in the widget with the new unit
setValue(currentValue);
}
else
{
// If current value is invalid, just clear field
setText("");
}
setValid(true);
amount->setDisplayUnit(newUnit);
}
void BitcoinAmountField::setDisplayUnit(int newUnit)
@ -226,6 +287,5 @@ void BitcoinAmountField::setDisplayUnit(int newUnit) @@ -226,6 +287,5 @@ void BitcoinAmountField::setDisplayUnit(int newUnit)
void BitcoinAmountField::setSingleStep(qint64 step)
{
nSingleStep = step;
unitChanged(unit->currentIndex());
amount->setSingleStep(step);
}

14
src/qt/bitcoinamountfield.h

@ -8,17 +8,18 @@ @@ -8,17 +8,18 @@
#include <QWidget>
QT_BEGIN_NAMESPACE
class QDoubleSpinBox;
class QValueComboBox;
QT_END_NAMESPACE
class AmountSpinBox;
/** Widget for entering bitcoin amounts.
*/
class BitcoinAmountField: public QWidget
{
Q_OBJECT
Q_PROPERTY(qint64 value READ value WRITE setValue NOTIFY textChanged USER true)
Q_PROPERTY(qint64 value READ value WRITE setValue NOTIFY valueChanged USER true)
public:
explicit BitcoinAmountField(QWidget *parent = 0);
@ -49,20 +50,15 @@ public: @@ -49,20 +50,15 @@ public:
QWidget *setupTabChain(QWidget *prev);
signals:
void textChanged();
void valueChanged();
protected:
/** Intercept focus-in event and ',' key presses */
bool eventFilter(QObject *object, QEvent *event);
private:
QDoubleSpinBox *amount;
AmountSpinBox *amount;
QValueComboBox *unit;
int currentUnit;
qint64 nSingleStep;
void setText(const QString &text);
QString text() const;
private slots:
void unitChanged(int idx);

29
src/qt/bitcoinunits.cpp

@ -4,6 +4,8 @@ @@ -4,6 +4,8 @@
#include "bitcoinunits.h"
#include "core.h"
#include <QStringList>
BitcoinUnits::BitcoinUnits(QObject *parent):
@ -78,28 +80,6 @@ qint64 BitcoinUnits::factor(int unit) @@ -78,28 +80,6 @@ qint64 BitcoinUnits::factor(int unit)
}
}
qint64 BitcoinUnits::maxAmount(int unit)
{
switch(unit)
{
case BTC: return Q_INT64_C(21000000);
case mBTC: return Q_INT64_C(21000000000);
case uBTC: return Q_INT64_C(21000000000000);
default: return 0;
}
}
int BitcoinUnits::amountDigits(int unit)
{
switch(unit)
{
case BTC: return 8; // 21,000,000 (# digits, without commas)
case mBTC: return 11; // 21,000,000,000
case uBTC: return 14; // 21,000,000,000,000
default: return 0;
}
}
int BitcoinUnits::decimals(int unit)
{
switch(unit)
@ -250,3 +230,8 @@ QVariant BitcoinUnits::data(const QModelIndex &index, int role) const @@ -250,3 +230,8 @@ QVariant BitcoinUnits::data(const QModelIndex &index, int role) const
}
return QVariant();
}
qint64 BitcoinUnits::maxMoney()
{
return MAX_MONEY;
}

7
src/qt/bitcoinunits.h

@ -82,10 +82,6 @@ public: @@ -82,10 +82,6 @@ public:
static QString description(int unit);
//! Number of Satoshis (1e-8) per unit
static qint64 factor(int unit);
//! Max amount per unit
static qint64 maxAmount(int unit);
//! Number of amount digits (to represent max number of coins)
static int amountDigits(int unit);
//! Number of decimals left
static int decimals(int unit);
//! Format as string
@ -120,6 +116,9 @@ public: @@ -120,6 +116,9 @@ public:
return text;
}
//! Return maximum number of base units (Satoshis)
static qint64 maxMoney();
private:
QList<BitcoinUnits::Unit> unitlist;
};

18
src/qt/sendcoinsentry.cpp

@ -34,6 +34,12 @@ SendCoinsEntry::SendCoinsEntry(QWidget *parent) : @@ -34,6 +34,12 @@ SendCoinsEntry::SendCoinsEntry(QWidget *parent) :
GUIUtil::setupAddressWidget(ui->payTo, this);
// just a label for displaying bitcoin address(es)
ui->payTo_is->setFont(GUIUtil::bitcoinAddressFont());
// Connect signals
connect(ui->payAmount, SIGNAL(valueChanged()), this, SIGNAL(payAmountChanged()));
connect(ui->deleteButton, SIGNAL(clicked()), this, SLOT(deleteClicked()));
connect(ui->deleteButton_is, SIGNAL(clicked()), this, SLOT(deleteClicked()));
connect(ui->deleteButton_s, SIGNAL(clicked()), this, SLOT(deleteClicked()));
}
SendCoinsEntry::~SendCoinsEntry()
@ -72,11 +78,6 @@ void SendCoinsEntry::setModel(WalletModel *model) @@ -72,11 +78,6 @@ void SendCoinsEntry::setModel(WalletModel *model)
if (model && model->getOptionsModel())
connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
connect(ui->payAmount, SIGNAL(textChanged()), this, SIGNAL(payAmountChanged()));
connect(ui->deleteButton, SIGNAL(clicked()), this, SLOT(deleteClicked()));
connect(ui->deleteButton_is, SIGNAL(clicked()), this, SLOT(deleteClicked()));
connect(ui->deleteButton_s, SIGNAL(clicked()), this, SLOT(deleteClicked()));
clear();
}
@ -130,6 +131,13 @@ bool SendCoinsEntry::validate() @@ -130,6 +131,13 @@ bool SendCoinsEntry::validate()
retval = false;
}
// Sending a zero amount is invalid
if (ui->payAmount->value(0) <= 0)
{
ui->payAmount->setValid(false);
retval = false;
}
// Reject dust outputs:
if (retval && GUIUtil::isDust(ui->payTo->text(), ui->payAmount->value())) {
ui->payAmount->setValid(false);

Loading…
Cancel
Save