1 // Copyright (c) 2011-2014 The Bitcoin developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 #include "bitcoinamountfield.h"
7 #include "bitcoinunits.h"
8 #include "guiconstants.h"
9 #include "qvaluecombobox.h"
11 #include <QApplication>
12 #include <QAbstractSpinBox>
13 #include <QHBoxLayout>
17 /** QSpinBox that uses fixed-point numbers internally and uses our own
18 * formatting/parsing functions.
20 class AmountSpinBox: public QAbstractSpinBox
24 explicit AmountSpinBox(QWidget *parent):
25 QAbstractSpinBox(parent),
26 currentUnit(BitcoinUnits::BTC),
27 singleStep(100000) // satoshis
29 setAlignment(Qt::AlignRight);
31 connect(lineEdit(), SIGNAL(textEdited(QString)), this, SIGNAL(valueChanged()));
34 QValidator::State validate(QString &text, int &pos) const
37 return QValidator::Intermediate;
40 /* Make sure we return Intermediate so that fixup() is called on defocus */
41 return valid ? QValidator::Intermediate : QValidator::Invalid;
44 void fixup(QString &input) const
47 CAmount val = parse(input, &valid);
50 input = BitcoinUnits::format(currentUnit, val, false, BitcoinUnits::separatorAlways);
51 lineEdit()->setText(input);
55 CAmount value(bool *valid_out=0) const
57 return parse(text(), valid_out);
60 void setValue(const CAmount& value)
62 lineEdit()->setText(BitcoinUnits::format(currentUnit, value, false, BitcoinUnits::separatorAlways));
66 void stepBy(int steps)
69 CAmount val = value(&valid);
70 val = val + steps * singleStep;
71 val = qMin(qMax(val, CAmount(0)), BitcoinUnits::maxMoney());
75 StepEnabled stepEnabled() const
78 if(text().isEmpty()) // Allow step-up with empty field
81 CAmount val = value(&valid);
85 rv |= StepDownEnabled;
86 if(val < BitcoinUnits::maxMoney())
92 void setDisplayUnit(int unit)
95 CAmount val = value(&valid);
105 void setSingleStep(const CAmount& step)
110 QSize minimumSizeHint() const
112 if(cachedMinimumSizeHint.isEmpty())
116 const QFontMetrics fm(fontMetrics());
117 int h = lineEdit()->minimumSizeHint().height();
118 int w = fm.width(BitcoinUnits::format(BitcoinUnits::BTC, BitcoinUnits::maxMoney(), false, BitcoinUnits::separatorAlways));
119 w += 2; // cursor blinking space
121 QStyleOptionSpinBox opt;
122 initStyleOption(&opt);
125 opt.rect.setSize(hint + extra);
126 extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &opt,
127 QStyle::SC_SpinBoxEditField, this).size();
128 // get closer to final result by repeating the calculation
129 opt.rect.setSize(hint + extra);
130 extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &opt,
131 QStyle::SC_SpinBoxEditField, this).size();
137 cachedMinimumSizeHint = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this)
138 .expandedTo(QApplication::globalStrut());
140 return cachedMinimumSizeHint;
145 mutable QSize cachedMinimumSizeHint;
148 * Parse a string into a number of base monetary units and
150 * @note Must return 0 if !valid.
152 CAmount parse(const QString &text, bool *valid_out=0) const
155 bool valid = BitcoinUnits::parse(currentUnit, text, &val);
158 if(val < 0 || val > BitcoinUnits::maxMoney())
163 return valid ? val : 0;
167 bool event(QEvent *event)
169 if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease)
171 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
172 if (keyEvent->key() == Qt::Key_Comma)
174 // Translate a comma into a period
175 QKeyEvent periodKeyEvent(event->type(), Qt::Key_Period, keyEvent->modifiers(), ".", keyEvent->isAutoRepeat(), keyEvent->count());
176 return QAbstractSpinBox::event(&periodKeyEvent);
179 return QAbstractSpinBox::event(event);
186 #include "bitcoinamountfield.moc"
188 BitcoinAmountField::BitcoinAmountField(QWidget *parent) :
192 amount = new AmountSpinBox(this);
193 amount->setLocale(QLocale::c());
194 amount->installEventFilter(this);
195 amount->setMaximumWidth(170);
197 QHBoxLayout *layout = new QHBoxLayout(this);
198 layout->addWidget(amount);
199 unit = new QValueComboBox(this);
200 unit->setModel(new BitcoinUnits(this));
201 layout->addWidget(unit);
202 layout->addStretch(1);
203 layout->setContentsMargins(0,0,0,0);
207 setFocusPolicy(Qt::TabFocus);
208 setFocusProxy(amount);
210 // If one if the widgets changes, the combined content changes as well
211 connect(amount, SIGNAL(valueChanged()), this, SIGNAL(valueChanged()));
212 connect(unit, SIGNAL(currentIndexChanged(int)), this, SLOT(unitChanged(int)));
214 // Set default based on configuration
215 unitChanged(unit->currentIndex());
218 void BitcoinAmountField::clear()
221 unit->setCurrentIndex(0);
224 void BitcoinAmountField::setEnabled(bool fEnabled)
226 amount->setEnabled(fEnabled);
227 unit->setEnabled(fEnabled);
230 bool BitcoinAmountField::validate()
238 void BitcoinAmountField::setValid(bool valid)
241 amount->setStyleSheet("");
243 amount->setStyleSheet(STYLE_INVALID);
246 bool BitcoinAmountField::eventFilter(QObject *object, QEvent *event)
248 if (event->type() == QEvent::FocusIn)
250 // Clear invalid flag on focus
253 return QWidget::eventFilter(object, event);
256 QWidget *BitcoinAmountField::setupTabChain(QWidget *prev)
258 QWidget::setTabOrder(prev, amount);
259 QWidget::setTabOrder(amount, unit);
263 CAmount BitcoinAmountField::value(bool *valid_out) const
265 return amount->value(valid_out);
268 void BitcoinAmountField::setValue(const CAmount& value)
270 amount->setValue(value);
273 void BitcoinAmountField::setReadOnly(bool fReadOnly)
275 amount->setReadOnly(fReadOnly);
276 unit->setEnabled(!fReadOnly);
279 void BitcoinAmountField::unitChanged(int idx)
281 // Use description tooltip for current unit for the combobox
282 unit->setToolTip(unit->itemData(idx, Qt::ToolTipRole).toString());
284 // Determine new unit ID
285 int newUnit = unit->itemData(idx, BitcoinUnits::UnitRole).toInt();
287 amount->setDisplayUnit(newUnit);
290 void BitcoinAmountField::setDisplayUnit(int newUnit)
292 unit->setValue(newUnit);
295 void BitcoinAmountField::setSingleStep(const CAmount& step)
297 amount->setSingleStep(step);