Bitcoin Core  27.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 
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 }
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:911
assert(!tx.IsCoinBase())