Bitcoin Core  28.1.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 
24 class AmountSpinBox: public QAbstractSpinBox
25 {
26  Q_OBJECT
27 
28 public:
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 
83  void SetMinValue(const CAmount& value)
84  {
86  }
87 
88  void SetMaxValue(const CAmount& value)
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;
108  lineEdit()->setPlaceholderText(BitcoinUnits::format(currentUnit, m_min_amount, false, BitcoinUnits::SeparatorStyle::ALWAYS));
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  }
149  return cachedMinimumSizeHint;
150  }
151 
152 private:
154  CAmount singleStep{CAmount(100000)}; // satoshis
155  mutable QSize cachedMinimumSizeHint;
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 
179 protected:
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 
214 Q_SIGNALS:
215  void valueChanged();
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 
277 bool 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 
287 QWidget *BitcoinAmountField::setupTabChain(QWidget *prev)
288 {
289  QWidget::setTabOrder(prev, amount);
290  QWidget::setTabOrder(amount, unit);
291  return unit;
292 }
293 
294 CAmount 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 }
bool validate()
Perform input validation, mark field as invalid if entered value is not valid.
Unit
Bitcoin units.
Definition: bitcoinunits.h:42
Bitcoin unit definitions.
Definition: bitcoinunits.h:32
void setReadOnly(bool fReadOnly)
Make read-only.
assert(!tx.IsCoinBase())
QSpinBox that uses fixed-point numbers internally and uses our own formatting/parsing functions...
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
void SetAllowEmpty(bool allow)
void valueChanged()
QSize minimumSizeHint() const override
AmountSpinBox(QWidget *parent)
QWidget * setupTabChain(QWidget *prev)
Qt messes up the tab chain by default in some cases (issue https://bugreports.qt-project.org/browse/QTBUG-10907), in these cases we have to set it up manually.
void setDisplayUnit(BitcoinUnit unit)
StepEnabled stepEnabled() const override
static bool parse(Unit unit, const QString &value, CAmount *val_out)
Parse string to coin amount.
State
The various states a (txhash,peer) pair can be in.
Definition: txrequest.cpp:42
CAmount value(bool *valid_out=nullptr) const
void fixup(QString &input) const override
BitcoinAmountField(QWidget *parent=nullptr)
QValidator::State validate(QString &text, int &pos) const override
void setValue(const CAmount &value)
int64_t CAmount
Amount in satoshis (Can be negative)
Definition: amount.h:12
AmountSpinBox * amount
void setEnabled(bool fEnabled)
Enable/Disable.
static QString format(Unit unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD, bool justify=false)
Format as string.
#define STYLE_INVALID
Definition: guiconstants.h:28
bool event(QEvent *event) override
Unit identifier.
Definition: bitcoinunits.h:92
void SetMinValue(const CAmount &value)
Set the minimum value in satoshis.
bool eventFilter(QObject *object, QEvent *event) override
Intercept focus-in event and &#39;,&#39; key presses.
void setSingleStep(const CAmount &step)
Set single step in satoshis.
QValueComboBox * unit
void setValue(const QVariant &value)
void SetMaxValue(const CAmount &value)
void clear()
Make field empty and ready for new input.
void setValid(bool valid)
Mark current value as invalid in UI.
void setSingleStep(const CAmount &step)
void SetMaxValue(const CAmount &value)
Set the maximum value in satoshis.
void SetMinValue(const CAmount &value)
void stepBy(int steps) 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 setDisplayUnit(BitcoinUnit new_unit)
Change unit used to display amount.
void setValue(const CAmount &value)
static CAmount maxMoney()
Return maximum number of base units (Satoshis)
BitcoinUnit currentUnit
void SetAllowEmpty(bool allow)
If allow empty is set to false the field will be set to the minimum allowed value if left empty...