Skip to content

Commit 7bf8ab9

Browse files
committed
Merge pull request bitcoin#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)
2 parents d5a3fd1 + 29eaa31 commit 7bf8ab9

File tree

6 files changed

+195
-146
lines changed

6 files changed

+195
-146
lines changed

src/Makefile.qt.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ BITCOIN_MM = \
145145

146146
QT_MOC = \
147147
qt/bitcoin.moc \
148+
qt/bitcoinamountfield.moc \
148149
qt/intro.moc \
149150
qt/overviewpage.moc \
150151
qt/rpcconsole.moc

src/qt/bitcoinamountfield.cpp

Lines changed: 166 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -9,63 +9,185 @@
99
#include "qvaluecombobox.h"
1010

1111
#include <QApplication>
12-
#include <QDoubleSpinBox>
12+
#include <QAbstractSpinBox>
1313
#include <QHBoxLayout>
1414
#include <QKeyEvent>
15-
#include <qmath.h> // for qPow()
15+
#include <QLineEdit>
1616

17-
// QDoubleSpinBox that shows SI-style thin space thousands separators
18-
class AmountSpinBox: public QDoubleSpinBox
17+
/** QSpinBox that uses fixed-point numbers internally and uses our own
18+
* formatting/parsing functions.
19+
*/
20+
class AmountSpinBox: public QAbstractSpinBox
1921
{
22+
Q_OBJECT
2023
public:
2124
explicit AmountSpinBox(QWidget *parent):
22-
QDoubleSpinBox(parent)
25+
QAbstractSpinBox(parent),
26+
currentUnit(BitcoinUnits::BTC),
27+
singleStep(100000) // satoshis
2328
{
29+
setAlignment(Qt::AlignRight);
30+
31+
connect(lineEdit(), SIGNAL(textEdited(QString)), this, SIGNAL(valueChanged()));
32+
}
33+
34+
QValidator::State validate(QString &text, int &pos) const
35+
{
36+
if(text.isEmpty())
37+
return QValidator::Intermediate;
38+
bool valid = false;
39+
parse(text, &valid);
40+
/* Make sure we return Intermediate so that fixup() is called on defocus */
41+
return valid ? QValidator::Intermediate : QValidator::Invalid;
42+
}
43+
44+
void fixup(QString &input) const
45+
{
46+
bool valid = false;
47+
qint64 val = parse(input, &valid);
48+
if(valid)
49+
{
50+
input = BitcoinUnits::format(currentUnit, val, false, BitcoinUnits::separatorAlways);
51+
lineEdit()->setText(input);
52+
}
2453
}
25-
QString textFromValue(double value) const
54+
55+
qint64 value(bool *valid_out=0) const
2656
{
27-
QStringList parts = QDoubleSpinBox::textFromValue(value).split(".");
28-
QString quotient_str = parts[0];
29-
QString remainder_str;
30-
if(parts.size() > 1)
31-
remainder_str = parts[1];
32-
33-
// Code duplication between here and BitcoinUnits::format
34-
// TODO: Figure out how to share this code
35-
QChar thin_sp(THIN_SP_CP);
36-
int q_size = quotient_str.size();
37-
if (q_size > 4)
38-
for (int i = 3; i < q_size; i += 3)
39-
quotient_str.insert(q_size - i, thin_sp);
40-
41-
int r_size = remainder_str.size();
42-
if (r_size > 4)
43-
for (int i = 3, adj = 0; i < r_size; i += 3, adj++)
44-
remainder_str.insert(i + adj, thin_sp);
45-
46-
if(remainder_str.isEmpty())
47-
return quotient_str;
57+
return parse(text(), valid_out);
58+
}
59+
60+
void setValue(qint64 value)
61+
{
62+
lineEdit()->setText(BitcoinUnits::format(currentUnit, value, false, BitcoinUnits::separatorAlways));
63+
emit valueChanged();
64+
}
65+
66+
void stepBy(int steps)
67+
{
68+
bool valid = false;
69+
qint64 val = value(&valid);
70+
val = val + steps * singleStep;
71+
val = qMin(qMax(val, Q_INT64_C(0)), BitcoinUnits::maxMoney());
72+
setValue(val);
73+
}
74+
75+
StepEnabled stepEnabled() const
76+
{
77+
StepEnabled rv = 0;
78+
if(text().isEmpty()) // Allow step-up with empty field
79+
return StepUpEnabled;
80+
bool valid = false;
81+
qint64 val = value(&valid);
82+
if(valid)
83+
{
84+
if(val > 0)
85+
rv |= StepDownEnabled;
86+
if(val < BitcoinUnits::maxMoney())
87+
rv |= StepUpEnabled;
88+
}
89+
return rv;
90+
}
91+
92+
void setDisplayUnit(int unit)
93+
{
94+
bool valid = false;
95+
qint64 val = value(&valid);
96+
97+
currentUnit = unit;
98+
99+
if(valid)
100+
setValue(val);
48101
else
49-
return quotient_str + QString(".") + remainder_str;
102+
clear();
50103
}
51-
QValidator::State validate (QString &text, int &pos) const
104+
105+
void setSingleStep(qint64 step)
52106
{
53-
QString s(BitcoinUnits::removeSpaces(text));
54-
return QDoubleSpinBox::validate(s, pos);
107+
singleStep = step;
108+
}
109+
110+
QSize minimumSizeHint() const
111+
{
112+
if(cachedMinimumSizeHint.isEmpty())
113+
{
114+
ensurePolished();
115+
116+
const QFontMetrics fm(fontMetrics());
117+
int h = lineEdit()->minimumSizeHint().height();
118+
int w = fm.width(BitcoinUnits::format(BitcoinUnits::BTC, BitcoinUnits::maxMoney(), false, BitcoinUnits::separatorAlways));
119+
w += 2; // cursor blinking space
120+
121+
QStyleOptionSpinBox opt;
122+
initStyleOption(&opt);
123+
QSize hint(w, h);
124+
QSize extra(35, 6);
125+
opt.rect.setSize(hint + extra);
126+
extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &opt,
127+
QStyle::SC_SpinBoxEditField, this).size();
128+
// get closer to final result by repeating the calculation
129+
opt.rect.setSize(hint + extra);
130+
extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &opt,
131+
QStyle::SC_SpinBoxEditField, this).size();
132+
hint += extra;
133+
134+
opt.rect = rect();
135+
136+
cachedMinimumSizeHint = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this)
137+
.expandedTo(QApplication::globalStrut());
138+
}
139+
return cachedMinimumSizeHint;
140+
}
141+
private:
142+
int currentUnit;
143+
qint64 singleStep;
144+
mutable QSize cachedMinimumSizeHint;
145+
146+
/**
147+
* Parse a string into a number of base monetary units and
148+
* return validity.
149+
* @note Must return 0 if !valid.
150+
*/
151+
qint64 parse(const QString &text, bool *valid_out=0) const
152+
{
153+
qint64 val = 0;
154+
bool valid = BitcoinUnits::parse(currentUnit, text, &val);
155+
if(valid)
156+
{
157+
if(val < 0 || val > BitcoinUnits::maxMoney())
158+
valid = false;
159+
}
160+
if(valid_out)
161+
*valid_out = valid;
162+
return valid ? val : 0;
55163
}
56-
double valueFromText(const QString& text) const
164+
165+
protected:
166+
bool event(QEvent *event)
57167
{
58-
return QDoubleSpinBox::valueFromText(BitcoinUnits::removeSpaces(text));
168+
if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease)
169+
{
170+
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
171+
if (keyEvent->key() == Qt::Key_Comma)
172+
{
173+
// Translate a comma into a period
174+
QKeyEvent periodKeyEvent(event->type(), Qt::Key_Period, keyEvent->modifiers(), ".", keyEvent->isAutoRepeat(), keyEvent->count());
175+
return QAbstractSpinBox::event(&periodKeyEvent);
176+
}
177+
}
178+
return QAbstractSpinBox::event(event);
59179
}
180+
181+
signals:
182+
void valueChanged();
60183
};
61184

185+
#include "bitcoinamountfield.moc"
186+
62187
BitcoinAmountField::BitcoinAmountField(QWidget *parent) :
63188
QWidget(parent),
64-
amount(0),
65-
currentUnit(-1)
189+
amount(0)
66190
{
67-
nSingleStep = 100000; // satoshis
68-
69191
amount = new AmountSpinBox(this);
70192
amount->setLocale(QLocale::c());
71193
amount->installEventFilter(this);
@@ -85,21 +207,13 @@ BitcoinAmountField::BitcoinAmountField(QWidget *parent) :
85207
setFocusProxy(amount);
86208

87209
// If one if the widgets changes, the combined content changes as well
88-
connect(amount, SIGNAL(valueChanged(QString)), this, SIGNAL(textChanged()));
210+
connect(amount, SIGNAL(valueChanged()), this, SIGNAL(valueChanged()));
89211
connect(unit, SIGNAL(currentIndexChanged(int)), this, SLOT(unitChanged(int)));
90212

91213
// Set default based on configuration
92214
unitChanged(unit->currentIndex());
93215
}
94216

95-
void BitcoinAmountField::setText(const QString &text)
96-
{
97-
if (text.isEmpty())
98-
amount->clear();
99-
else
100-
amount->setValue(BitcoinUnits::removeSpaces(text).toDouble());
101-
}
102-
103217
void BitcoinAmountField::clear()
104218
{
105219
amount->clear();
@@ -108,16 +222,9 @@ void BitcoinAmountField::clear()
108222

109223
bool BitcoinAmountField::validate()
110224
{
111-
bool valid = true;
112-
if (amount->value() == 0.0)
113-
valid = false;
114-
else if (!BitcoinUnits::parse(currentUnit, text(), 0))
115-
valid = false;
116-
else if (amount->value() > BitcoinUnits::maxAmount(currentUnit))
117-
valid = false;
118-
225+
bool valid = false;
226+
value(&valid);
119227
setValid(valid);
120-
121228
return valid;
122229
}
123230

@@ -129,32 +236,13 @@ void BitcoinAmountField::setValid(bool valid)
129236
amount->setStyleSheet(STYLE_INVALID);
130237
}
131238

132-
QString BitcoinAmountField::text() const
133-
{
134-
if (amount->text().isEmpty())
135-
return QString();
136-
else
137-
return amount->text();
138-
}
139-
140239
bool BitcoinAmountField::eventFilter(QObject *object, QEvent *event)
141240
{
142241
if (event->type() == QEvent::FocusIn)
143242
{
144243
// Clear invalid flag on focus
145244
setValid(true);
146245
}
147-
else if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease)
148-
{
149-
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
150-
if (keyEvent->key() == Qt::Key_Comma)
151-
{
152-
// Translate a comma into a period
153-
QKeyEvent periodKeyEvent(event->type(), Qt::Key_Period, keyEvent->modifiers(), ".", keyEvent->isAutoRepeat(), keyEvent->count());
154-
QApplication::sendEvent(object, &periodKeyEvent);
155-
return true;
156-
}
157-
}
158246
return QWidget::eventFilter(object, event);
159247
}
160248

@@ -167,18 +255,12 @@ QWidget *BitcoinAmountField::setupTabChain(QWidget *prev)
167255

168256
qint64 BitcoinAmountField::value(bool *valid_out) const
169257
{
170-
qint64 val_out = 0;
171-
bool valid = BitcoinUnits::parse(currentUnit, text(), &val_out);
172-
if (valid_out)
173-
{
174-
*valid_out = valid;
175-
}
176-
return val_out;
258+
return amount->value(valid_out);
177259
}
178260

179261
void BitcoinAmountField::setValue(qint64 value)
180262
{
181-
setText(BitcoinUnits::format(currentUnit, value));
263+
amount->setValue(value);
182264
}
183265

184266
void BitcoinAmountField::setReadOnly(bool fReadOnly)
@@ -195,28 +277,7 @@ void BitcoinAmountField::unitChanged(int idx)
195277
// Determine new unit ID
196278
int newUnit = unit->itemData(idx, BitcoinUnits::UnitRole).toInt();
197279

198-
// Parse current value and convert to new unit
199-
bool valid = false;
200-
qint64 currentValue = value(&valid);
201-
202-
currentUnit = newUnit;
203-
204-
// Set max length after retrieving the value, to prevent truncation
205-
amount->setDecimals(BitcoinUnits::decimals(currentUnit));
206-
amount->setMaximum(qPow(10, BitcoinUnits::amountDigits(currentUnit)) - qPow(10, -amount->decimals()));
207-
amount->setSingleStep((double)nSingleStep / (double)BitcoinUnits::factor(currentUnit));
208-
209-
if (valid)
210-
{
211-
// If value was valid, re-place it in the widget with the new unit
212-
setValue(currentValue);
213-
}
214-
else
215-
{
216-
// If current value is invalid, just clear field
217-
setText("");
218-
}
219-
setValid(true);
280+
amount->setDisplayUnit(newUnit);
220281
}
221282

222283
void BitcoinAmountField::setDisplayUnit(int newUnit)
@@ -226,6 +287,5 @@ void BitcoinAmountField::setDisplayUnit(int newUnit)
226287

227288
void BitcoinAmountField::setSingleStep(qint64 step)
228289
{
229-
nSingleStep = step;
230-
unitChanged(unit->currentIndex());
290+
amount->setSingleStep(step);
231291
}

0 commit comments

Comments
 (0)