Bitcoin Core  0.20.99
P2P Digital Currency
bitcoinamountfield.cpp
Go to the documentation of this file.
1 // Copyright (c) 2011-2019 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  {
71  lineEdit()->setText(BitcoinUnits::format(currentUnit, value, false, BitcoinUnits::SeparatorStyle::ALWAYS));
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 {
300  amount->setValue(value);
301 }
302 
304 {
305  amount->SetAllowEmpty(allow);
306 }
307 
309 {
310  amount->SetMinValue(value);
311 }
312 
314 {
315  amount->SetMaxValue(value);
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 }
bool validate()
Perform input validation, mark field as invalid if entered value is not valid.
Bitcoin unit definitions.
Definition: bitcoinunits.h:31
void setReadOnly(bool fReadOnly)
Make read-only.
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:893
void SetAllowEmpty(bool allow)
void valueChanged()
static QString format(int unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD, bool justify=false)
Format as string.
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.
StepEnabled stepEnabled() const override
void setDisplayUnit(int unit)
CAmount value(bool *valid_out=nullptr) const
void fixup(QString &input) const override
static bool parse(int unit, const QString &value, CAmount *val_out)
Parse string to coin amount.
disconnectpool queuedTx clear()
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.
#define STYLE_INVALID
Definition: guiconstants.h:22
bool event(QEvent *event) override
Unit identifier.
Definition: bitcoinunits.h:93
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(int unit)
Change unit used to display amount.
void setValue(const CAmount &value)
static CAmount maxMoney()
Return maximum number of base units (Satoshis)
void SetAllowEmpty(bool allow)
If allow empty is set to false the field will be set to the minimum allowed value if left empty...