Bitcoin Core 22.99.0
P2P Digital Currency
bitcoinamountfield.cpp
Go to the documentation of this file.
1// Copyright (c) 2011-2020 The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
6
7#include <qt/bitcoinunits.h>
8#include <qt/guiconstants.h>
9#include <qt/guiutil.h>
10#include <qt/qvaluecombobox.h>
11
12#include <QApplication>
13#include <QAbstractSpinBox>
14#include <QHBoxLayout>
15#include <QKeyEvent>
16#include <QLineEdit>
17
21class AmountSpinBox: public QAbstractSpinBox
22{
23 Q_OBJECT
24
25public:
26 explicit AmountSpinBox(QWidget *parent):
27 QAbstractSpinBox(parent)
28 {
29 setAlignment(Qt::AlignRight);
30
31 connect(lineEdit(), &QLineEdit::textEdited, this, &AmountSpinBox::valueChanged);
32 }
33
34 QValidator::State validate(QString &text, int &pos) const override
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 override
45 {
46 bool valid;
47 CAmount val;
48
49 if (input.isEmpty() && !m_allow_empty) {
50 valid = true;
51 val = m_min_amount;
52 } else {
53 valid = false;
54 val = parse(input, &valid);
55 }
56
57 if (valid) {
58 val = qBound(m_min_amount, val, m_max_amount);
60 lineEdit()->setText(input);
61 }
62 }
63
64 CAmount value(bool *valid_out=nullptr) const
65 {
66 return parse(text(), valid_out);
67 }
68
69 void setValue(const CAmount& value)
70 {
72 Q_EMIT valueChanged();
73 }
74
75 void SetAllowEmpty(bool allow)
76 {
77 m_allow_empty = allow;
78 }
79
81 {
83 }
84
86 {
88 }
89
90 void stepBy(int steps) override
91 {
92 bool valid = false;
93 CAmount val = value(&valid);
94 val = val + steps * singleStep;
95 val = qBound(m_min_amount, val, m_max_amount);
96 setValue(val);
97 }
98
99 void setDisplayUnit(int unit)
100 {
101 bool valid = false;
102 CAmount val = value(&valid);
103
104 currentUnit = unit;
106 if(valid)
107 setValue(val);
108 else
109 clear();
110 }
111
112 void setSingleStep(const CAmount& step)
113 {
114 singleStep = step;
115 }
116
117 QSize minimumSizeHint() const override
118 {
119 if(cachedMinimumSizeHint.isEmpty())
120 {
121 ensurePolished();
122
123 const QFontMetrics fm(fontMetrics());
124 int h = lineEdit()->minimumSizeHint().height();
126 w += 2; // cursor blinking space
127
128 QStyleOptionSpinBox opt;
129 initStyleOption(&opt);
130 QSize hint(w, h);
131 QSize extra(35, 6);
132 opt.rect.setSize(hint + extra);
133 extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &opt,
134 QStyle::SC_SpinBoxEditField, this).size();
135 // get closer to final result by repeating the calculation
136 opt.rect.setSize(hint + extra);
137 extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &opt,
138 QStyle::SC_SpinBoxEditField, this).size();
139 hint += extra;
140 hint.setHeight(h);
141
142 opt.rect = rect();
143
144 cachedMinimumSizeHint = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this)
145 .expandedTo(QApplication::globalStrut());
146 }
148 }
149
150private:
152 CAmount singleStep{CAmount(100000)}; // satoshis
154 bool m_allow_empty{true};
157
163 CAmount parse(const QString &text, bool *valid_out=nullptr) const
164 {
165 CAmount val = 0;
166 bool valid = BitcoinUnits::parse(currentUnit, text, &val);
167 if(valid)
168 {
169 if(val < 0 || val > BitcoinUnits::maxMoney())
170 valid = false;
171 }
172 if(valid_out)
173 *valid_out = valid;
174 return valid ? val : 0;
175 }
176
177protected:
178 bool event(QEvent *event) override
179 {
180 if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease)
181 {
182 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
183 if (keyEvent->key() == Qt::Key_Comma)
184 {
185 // Translate a comma into a period
186 QKeyEvent periodKeyEvent(event->type(), Qt::Key_Period, keyEvent->modifiers(), ".", keyEvent->isAutoRepeat(), keyEvent->count());
187 return QAbstractSpinBox::event(&periodKeyEvent);
188 }
189 }
190 return QAbstractSpinBox::event(event);
191 }
192
193 StepEnabled stepEnabled() const override
194 {
195 if (isReadOnly()) // Disable steps when AmountSpinBox is read-only
196 return StepNone;
197 if (text().isEmpty()) // Allow step-up with empty field
198 return StepUpEnabled;
199
200 StepEnabled rv = StepNone;
201 bool valid = false;
202 CAmount val = value(&valid);
203 if (valid) {
204 if (val > m_min_amount)
205 rv |= StepDownEnabled;
206 if (val < m_max_amount)
207 rv |= StepUpEnabled;
208 }
209 return rv;
210 }
211
212Q_SIGNALS:
214};
215
216#include <qt/bitcoinamountfield.moc>
217
219 QWidget(parent),
220 amount(nullptr)
221{
222 amount = new AmountSpinBox(this);
223 amount->setLocale(QLocale::c());
224 amount->installEventFilter(this);
225 amount->setMaximumWidth(240);
226
227 QHBoxLayout *layout = new QHBoxLayout(this);
228 layout->addWidget(amount);
229 unit = new QValueComboBox(this);
230 unit->setModel(new BitcoinUnits(this));
231 layout->addWidget(unit);
232 layout->addStretch(1);
233 layout->setContentsMargins(0,0,0,0);
234
235 setLayout(layout);
236
237 setFocusPolicy(Qt::TabFocus);
238 setFocusProxy(amount);
239
240 // If one if the widgets changes, the combined content changes as well
242 connect(unit, qOverload<int>(&QComboBox::currentIndexChanged), this, &BitcoinAmountField::unitChanged);
243
244 // Set default based on configuration
245 unitChanged(unit->currentIndex());
246}
247
249{
250 amount->clear();
251 unit->setCurrentIndex(0);
252}
253
255{
256 amount->setEnabled(fEnabled);
257 unit->setEnabled(fEnabled);
258}
259
261{
262 bool valid = false;
263 value(&valid);
264 setValid(valid);
265 return valid;
266}
267
269{
270 if (valid)
271 amount->setStyleSheet("");
272 else
273 amount->setStyleSheet(STYLE_INVALID);
274}
275
276bool BitcoinAmountField::eventFilter(QObject *object, QEvent *event)
277{
278 if (event->type() == QEvent::FocusIn)
279 {
280 // Clear invalid flag on focus
281 setValid(true);
282 }
283 return QWidget::eventFilter(object, event);
284}
285
287{
288 QWidget::setTabOrder(prev, amount);
289 QWidget::setTabOrder(amount, unit);
290 return unit;
291}
292
293CAmount BitcoinAmountField::value(bool *valid_out) const
294{
295 return amount->value(valid_out);
296}
297
299{
301}
302
304{
305 amount->SetAllowEmpty(allow);
306}
307
309{
311}
312
314{
316}
317
319{
320 amount->setReadOnly(fReadOnly);
321}
322
324{
325 // Use description tooltip for current unit for the combobox
326 unit->setToolTip(unit->itemData(idx, Qt::ToolTipRole).toString());
327
328 // Determine new unit ID
329 int newUnit = unit->itemData(idx, BitcoinUnits::UnitRole).toInt();
330
331 amount->setDisplayUnit(newUnit);
332}
333
335{
336 unit->setValue(newUnit);
337}
338
340{
341 amount->setSingleStep(step);
342}
int64_t CAmount
Amount in satoshis (Can be negative)
Definition: amount.h:12
QSpinBox that uses fixed-point numbers internally and uses our own formatting/parsing functions.
void SetMinValue(const CAmount &value)
bool event(QEvent *event) override
void setSingleStep(const CAmount &step)
AmountSpinBox(QWidget *parent)
QValidator::State validate(QString &text, int &pos) const override
CAmount value(bool *valid_out=nullptr) const
StepEnabled stepEnabled() const override
void fixup(QString &input) const override
CAmount parse(const QString &text, bool *valid_out=nullptr) const
Parse a string into a number of base monetary units and return validity.
void valueChanged()
void stepBy(int steps) override
void SetMaxValue(const CAmount &value)
QSize minimumSizeHint() const override
void setValue(const CAmount &value)
void SetAllowEmpty(bool allow)
void setDisplayUnit(int unit)
AmountSpinBox * amount
void setEnabled(bool fEnabled)
Enable/Disable.
QValueComboBox * unit
void SetMaxValue(const CAmount &value)
Set the maximum value in satoshis.
void setSingleStep(const CAmount &step)
Set single step in satoshis.
void SetMinValue(const CAmount &value)
Set the minimum value in satoshis.
bool eventFilter(QObject *object, QEvent *event) override
Intercept focus-in event and ',' key presses.
void setReadOnly(bool fReadOnly)
Make read-only.
BitcoinAmountField(QWidget *parent=nullptr)
void setDisplayUnit(int unit)
Change unit used to display amount.
void clear()
Make field empty and ready for new input.
bool validate()
Perform input validation, mark field as invalid if entered value is not valid.
QWidget * setupTabChain(QWidget *prev)
Qt messes up the tab chain by default in some cases (issue https://bugreports.qt-project....
void setValid(bool valid)
Mark current value as invalid in UI.
void setValue(const CAmount &value)
void SetAllowEmpty(bool allow)
If allow empty is set to false the field will be set to the minimum allowed value if left empty.
Bitcoin unit definitions.
Definition: bitcoinunits.h:32
@ UnitRole
Unit identifier.
Definition: bitcoinunits.h:93
static bool parse(int unit, const QString &value, CAmount *val_out)
Parse string to coin amount.
static CAmount maxMoney()
Return maximum number of base units (Satoshis)
static QString format(int unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD, bool justify=false)
Format as string.
void setValue(const QVariant &value)
#define STYLE_INVALID
Definition: guiconstants.h:22
int TextWidth(const QFontMetrics &fm, const QString &text)
Returns the distance in pixels appropriate for drawing a subsequent character after text.
Definition: guiutil.cpp:882