Bitcoin Core 28.99.0
P2P Digital Currency
bitcoinamountfield.cpp
Go to the documentation of this file.
1// Copyright (c) 2011-2022 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#include <QVariant>
18
19#include <cassert>
20
24class AmountSpinBox: public QAbstractSpinBox
25{
26 Q_OBJECT
27
28public:
29 explicit AmountSpinBox(QWidget *parent):
30 QAbstractSpinBox(parent)
31 {
32 setAlignment(Qt::AlignRight);
33
34 connect(lineEdit(), &QLineEdit::textEdited, this, &AmountSpinBox::valueChanged);
35 }
36
37 QValidator::State validate(QString &text, int &pos) const override
38 {
39 if(text.isEmpty())
40 return QValidator::Intermediate;
41 bool valid = false;
42 parse(text, &valid);
43 /* Make sure we return Intermediate so that fixup() is called on defocus */
44 return valid ? QValidator::Intermediate : QValidator::Invalid;
45 }
46
47 void fixup(QString &input) const override
48 {
49 bool valid;
50 CAmount val;
51
52 if (input.isEmpty() && !m_allow_empty) {
53 valid = true;
54 val = m_min_amount;
55 } else {
56 valid = false;
57 val = parse(input, &valid);
58 }
59
60 if (valid) {
61 val = qBound(m_min_amount, val, m_max_amount);
63 lineEdit()->setText(input);
64 }
65 }
66
67 CAmount value(bool *valid_out=nullptr) const
68 {
69 return parse(text(), valid_out);
70 }
71
72 void setValue(const CAmount& value)
73 {
75 Q_EMIT valueChanged();
76 }
77
78 void SetAllowEmpty(bool allow)
79 {
80 m_allow_empty = allow;
81 }
82
84 {
86 }
87
89 {
91 }
92
93 void stepBy(int steps) override
94 {
95 bool valid = false;
96 CAmount val = value(&valid);
97 val = val + steps * singleStep;
98 val = qBound(m_min_amount, val, m_max_amount);
99 setValue(val);
100 }
101
103 {
104 bool valid = false;
105 CAmount val = value(&valid);
106
107 currentUnit = unit;
109 if(valid)
110 setValue(val);
111 else
112 clear();
113 }
114
115 void setSingleStep(const CAmount& step)
116 {
117 singleStep = step;
118 }
119
120 QSize minimumSizeHint() const override
121 {
122 if(cachedMinimumSizeHint.isEmpty())
123 {
124 ensurePolished();
125
126 const QFontMetrics fm(fontMetrics());
127 int h = lineEdit()->minimumSizeHint().height();
129 w += 2; // cursor blinking space
130
131 QStyleOptionSpinBox opt;
132 initStyleOption(&opt);
133 QSize hint(w, h);
134 QSize extra(35, 6);
135 opt.rect.setSize(hint + extra);
136 extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &opt,
137 QStyle::SC_SpinBoxEditField, this).size();
138 // get closer to final result by repeating the calculation
139 opt.rect.setSize(hint + extra);
140 extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &opt,
141 QStyle::SC_SpinBoxEditField, this).size();
142 hint += extra;
143 hint.setHeight(h);
144
145 opt.rect = rect();
146
147 cachedMinimumSizeHint = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this);
148 }
150 }
151
152private:
154 CAmount singleStep{CAmount(100000)}; // satoshis
156 bool m_allow_empty{true};
159
165 CAmount parse(const QString &text, bool *valid_out=nullptr) const
166 {
167 CAmount val = 0;
168 bool valid = BitcoinUnits::parse(currentUnit, text, &val);
169 if(valid)
170 {
171 if(val < 0 || val > BitcoinUnits::maxMoney())
172 valid = false;
173 }
174 if(valid_out)
175 *valid_out = valid;
176 return valid ? val : 0;
177 }
178
179protected:
180 bool event(QEvent *event) override
181 {
182 if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease)
183 {
184 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
185 if (keyEvent->key() == Qt::Key_Comma)
186 {
187 // Translate a comma into a period
188 QKeyEvent periodKeyEvent(event->type(), Qt::Key_Period, keyEvent->modifiers(), ".", keyEvent->isAutoRepeat(), keyEvent->count());
189 return QAbstractSpinBox::event(&periodKeyEvent);
190 }
191 }
192 return QAbstractSpinBox::event(event);
193 }
194
195 StepEnabled stepEnabled() const override
196 {
197 if (isReadOnly()) // Disable steps when AmountSpinBox is read-only
198 return StepNone;
199 if (text().isEmpty()) // Allow step-up with empty field
200 return StepUpEnabled;
201
202 StepEnabled rv = StepNone;
203 bool valid = false;
204 CAmount val = value(&valid);
205 if (valid) {
206 if (val > m_min_amount)
207 rv |= StepDownEnabled;
208 if (val < m_max_amount)
209 rv |= StepUpEnabled;
210 }
211 return rv;
212 }
213
214Q_SIGNALS:
216};
217
218#include <qt/bitcoinamountfield.moc>
219
221 : QWidget(parent)
222{
223 amount = new AmountSpinBox(this);
224 amount->setLocale(QLocale::c());
225 amount->installEventFilter(this);
226 amount->setMaximumWidth(240);
227
228 QHBoxLayout *layout = new QHBoxLayout(this);
229 layout->addWidget(amount);
230 unit = new QValueComboBox(this);
231 unit->setModel(new BitcoinUnits(this));
232 layout->addWidget(unit);
233 layout->addStretch(1);
234 layout->setContentsMargins(0,0,0,0);
235
236 setLayout(layout);
237
238 setFocusPolicy(Qt::TabFocus);
239 setFocusProxy(amount);
240
241 // If one if the widgets changes, the combined content changes as well
243 connect(unit, qOverload<int>(&QComboBox::currentIndexChanged), this, &BitcoinAmountField::unitChanged);
244
245 // Set default based on configuration
246 unitChanged(unit->currentIndex());
247}
248
250{
251 amount->clear();
252 unit->setCurrentIndex(0);
253}
254
256{
257 amount->setEnabled(fEnabled);
258 unit->setEnabled(fEnabled);
259}
260
262{
263 bool valid = false;
264 value(&valid);
265 setValid(valid);
266 return valid;
267}
268
270{
271 if (valid)
272 amount->setStyleSheet("");
273 else
274 amount->setStyleSheet(STYLE_INVALID);
275}
276
277bool BitcoinAmountField::eventFilter(QObject *object, QEvent *event)
278{
279 if (event->type() == QEvent::FocusIn)
280 {
281 // Clear invalid flag on focus
282 setValid(true);
283 }
284 return QWidget::eventFilter(object, event);
285}
286
288{
289 QWidget::setTabOrder(prev, amount);
290 QWidget::setTabOrder(amount, unit);
291 return unit;
292}
293
294CAmount BitcoinAmountField::value(bool *valid_out) const
295{
296 return amount->value(valid_out);
297}
298
300{
302}
303
305{
306 amount->SetAllowEmpty(allow);
307}
308
310{
312}
313
315{
317}
318
320{
321 amount->setReadOnly(fReadOnly);
322}
323
325{
326 // Use description tooltip for current unit for the combobox
327 unit->setToolTip(unit->itemData(idx, Qt::ToolTipRole).toString());
328
329 // Determine new unit ID
330 QVariant new_unit = unit->currentData(BitcoinUnits::UnitRole);
331 assert(new_unit.isValid());
332 amount->setDisplayUnit(new_unit.value<BitcoinUnit>());
333}
334
336{
337 unit->setValue(QVariant::fromValue(new_unit));
338}
339
341{
342 amount->setSingleStep(step);
343}
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.
BitcoinUnit currentUnit
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)
void setDisplayUnit(BitcoinUnit unit)
QSize minimumSizeHint() const override
void setValue(const CAmount &value)
void SetAllowEmpty(bool allow)
AmountSpinBox * amount
void setEnabled(bool fEnabled)
Enable/Disable.
QValueComboBox * unit
void setDisplayUnit(BitcoinUnit new_unit)
Change unit used to display amount.
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 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:33
@ UnitRole
Unit identifier.
Definition: bitcoinunits.h:92
static CAmount maxMoney()
Return maximum number of base units (Satoshis)
static QString format(Unit unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD, bool justify=false)
Format as string.
static bool parse(Unit unit, const QString &value, CAmount *val_out)
Parse string to coin amount.
Unit
Bitcoin units.
Definition: bitcoinunits.h:42
void setValue(const QVariant &value)
#define STYLE_INVALID
Definition: guiconstants.h:28
int TextWidth(const QFontMetrics &fm, const QString &text)
Returns the distance in pixels appropriate for drawing a subsequent character after text.
Definition: guiutil.cpp:915
assert(!tx.IsCoinBase())