Bitcoin Core  21.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 
21 class AmountSpinBox: public QAbstractSpinBox
22 {
23  Q_OBJECT
24 
25 public:
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 
80  void SetMinValue(const CAmount& value)
81  {
83  }
84 
85  void SetMaxValue(const CAmount& value)
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;
105  lineEdit()->setPlaceholderText(BitcoinUnits::format(currentUnit, m_min_amount, false, BitcoinUnits::SeparatorStyle::ALWAYS));
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  }
147  return cachedMinimumSizeHint;
148  }
149 
150 private:
152  CAmount singleStep{CAmount(100000)}; // satoshis
153  mutable QSize cachedMinimumSizeHint;
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 
177 protected:
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 
212 Q_SIGNALS:
213  void valueChanged();
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, static_cast<void (QComboBox::*)(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 
276 bool 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 
286 QWidget *BitcoinAmountField::setupTabChain(QWidget *prev)
287 {
288  QWidget::setTabOrder(prev, amount);
289  QWidget::setTabOrder(amount, unit);
290  return unit;
291 }
292 
293 CAmount 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 }
AmountSpinBox::event
bool event(QEvent *event) override
Definition: bitcoinamountfield.cpp:178
AmountSpinBox::m_max_amount
CAmount m_max_amount
Definition: bitcoinamountfield.cpp:156
BitcoinAmountField::SetAllowEmpty
void SetAllowEmpty(bool allow)
If allow empty is set to false the field will be set to the minimum allowed value if left empty.
Definition: bitcoinamountfield.cpp:303
AmountSpinBox::singleStep
CAmount singleStep
Definition: bitcoinamountfield.cpp:152
clear
disconnectpool queuedTx clear()
QValueComboBox::setValue
void setValue(const QVariant &value)
Definition: qvaluecombobox.cpp:18
BitcoinAmountField::unitChanged
void unitChanged(int idx)
Definition: bitcoinamountfield.cpp:323
BitcoinAmountField::valueChanged
void valueChanged()
BitcoinAmountField::setReadOnly
void setReadOnly(bool fReadOnly)
Make read-only.
Definition: bitcoinamountfield.cpp:318
BitcoinAmountField::unit
QValueComboBox * unit
Definition: bitcoinamountfield.h:77
BitcoinAmountField::setDisplayUnit
void setDisplayUnit(int unit)
Change unit used to display amount.
Definition: bitcoinamountfield.cpp:334
AmountSpinBox::m_allow_empty
bool m_allow_empty
Definition: bitcoinamountfield.cpp:154
AmountSpinBox::SetMaxValue
void SetMaxValue(const CAmount &value)
Definition: bitcoinamountfield.cpp:85
BitcoinUnits::maxMoney
static CAmount maxMoney()
Return maximum number of base units (Satoshis)
Definition: bitcoinunits.cpp:247
AmountSpinBox::SetMinValue
void SetMinValue(const CAmount &value)
Definition: bitcoinamountfield.cpp:80
AmountSpinBox::currentUnit
int currentUnit
Definition: bitcoinamountfield.cpp:151
AmountSpinBox::setSingleStep
void setSingleStep(const CAmount &step)
Definition: bitcoinamountfield.cpp:112
BitcoinUnits::parse
static bool parse(int unit, const QString &value, CAmount *val_out)
Parse string to coin amount.
Definition: bitcoinunits.cpp:172
QValueComboBox
Definition: qvaluecombobox.h:12
qvaluecombobox.h
BitcoinAmountField::SetMaxValue
void SetMaxValue(const CAmount &value)
Set the maximum value in satoshis.
Definition: bitcoinamountfield.cpp:313
AmountSpinBox::parse
CAmount parse(const QString &text, bool *valid_out=nullptr) const
Parse a string into a number of base monetary units and return validity.
Definition: bitcoinamountfield.cpp:163
AmountSpinBox::stepBy
void stepBy(int steps) override
Definition: bitcoinamountfield.cpp:90
BitcoinAmountField::value
qint64 value
Definition: bitcoinamountfield.h:26
BitcoinUnits
Bitcoin unit definitions.
Definition: bitcoinunits.h:31
BitcoinAmountField::setupTabChain
QWidget * setupTabChain(QWidget *prev)
Qt messes up the tab chain by default in some cases (issue https://bugreports.qt-project....
Definition: bitcoinamountfield.cpp:286
CAmount
int64_t CAmount
Amount in satoshis (Can be negative)
Definition: amount.h:12
BitcoinAmountField::eventFilter
bool eventFilter(QObject *object, QEvent *event) override
Intercept focus-in event and ',' key presses.
Definition: bitcoinamountfield.cpp:276
guiutil.h
AmountSpinBox
QSpinBox that uses fixed-point numbers internally and uses our own formatting/parsing functions.
Definition: bitcoinamountfield.cpp:21
AmountSpinBox::valueChanged
void valueChanged()
AmountSpinBox::SetAllowEmpty
void SetAllowEmpty(bool allow)
Definition: bitcoinamountfield.cpp:75
BitcoinUnits::SeparatorStyle::ALWAYS
@ ALWAYS
AmountSpinBox::minimumSizeHint
QSize minimumSizeHint() const override
Definition: bitcoinamountfield.cpp:117
BitcoinAmountField::setValue
void setValue(const CAmount &value)
Definition: bitcoinamountfield.cpp:298
bitcoinamountfield.h
AmountSpinBox::AmountSpinBox
AmountSpinBox(QWidget *parent)
Definition: bitcoinamountfield.cpp:26
BitcoinAmountField::clear
void clear()
Make field empty and ready for new input.
Definition: bitcoinamountfield.cpp:248
BitcoinAmountField::setSingleStep
void setSingleStep(const CAmount &step)
Set single step in satoshis.
Definition: bitcoinamountfield.cpp:339
guiconstants.h
BitcoinAmountField::amount
AmountSpinBox * amount
Definition: bitcoinamountfield.h:76
AmountSpinBox::value
CAmount value(bool *valid_out=nullptr) const
Definition: bitcoinamountfield.cpp:64
BitcoinAmountField::setValid
void setValid(bool valid)
Mark current value as invalid in UI.
Definition: bitcoinamountfield.cpp:268
AmountSpinBox::setDisplayUnit
void setDisplayUnit(int unit)
Definition: bitcoinamountfield.cpp:99
bitcoinunits.h
AmountSpinBox::stepEnabled
StepEnabled stepEnabled() const override
Definition: bitcoinamountfield.cpp:193
GUIUtil::TextWidth
int TextWidth(const QFontMetrics &fm, const QString &text)
Returns the distance in pixels appropriate for drawing a subsequent character after text.
Definition: guiutil.cpp:821
BitcoinAmountField::BitcoinAmountField
BitcoinAmountField(QWidget *parent=nullptr)
Definition: bitcoinamountfield.cpp:218
BitcoinAmountField::SetMinValue
void SetMinValue(const CAmount &value)
Set the minimum value in satoshis.
Definition: bitcoinamountfield.cpp:308
AmountSpinBox::m_min_amount
CAmount m_min_amount
Definition: bitcoinamountfield.cpp:155
AmountSpinBox::fixup
void fixup(QString &input) const override
Definition: bitcoinamountfield.cpp:44
STYLE_INVALID
#define STYLE_INVALID
Definition: guiconstants.h:22
AmountSpinBox::cachedMinimumSizeHint
QSize cachedMinimumSizeHint
Definition: bitcoinamountfield.cpp:153
AmountSpinBox::validate
QValidator::State validate(QString &text, int &pos) const override
Definition: bitcoinamountfield.cpp:34
BitcoinUnits::UnitRole
@ UnitRole
Unit identifier.
Definition: bitcoinunits.h:93
BitcoinAmountField::validate
bool validate()
Perform input validation, mark field as invalid if entered value is not valid.
Definition: bitcoinamountfield.cpp:260
AmountSpinBox::setValue
void setValue(const CAmount &value)
Definition: bitcoinamountfield.cpp:69
BitcoinAmountField::setEnabled
void setEnabled(bool fEnabled)
Enable/Disable.
Definition: bitcoinamountfield.cpp:254
BitcoinUnits::BTC
@ BTC
Definition: bitcoinunits.h:43
BitcoinUnits::format
static QString format(int unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD, bool justify=false)
Format as string.
Definition: bitcoinunits.cpp:101