Line data Source code
1 : // Copyright (c) 2011-2014 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 :
5 : #include "bitcoinamountfield.h"
6 :
7 : #include "bitcoinunits.h"
8 : #include "guiconstants.h"
9 : #include "qvaluecombobox.h"
10 :
11 : #include <QApplication>
12 : #include <QAbstractSpinBox>
13 : #include <QHBoxLayout>
14 : #include <QKeyEvent>
15 : #include <QLineEdit>
16 :
17 : /** QSpinBox that uses fixed-point numbers internally and uses our own
18 : * formatting/parsing functions.
19 : */
20 0 : class AmountSpinBox: public QAbstractSpinBox
21 : {
22 : Q_OBJECT
23 :
24 : public:
25 0 : explicit AmountSpinBox(QWidget *parent):
26 : QAbstractSpinBox(parent),
27 : currentUnit(BitcoinUnits::BTC),
28 0 : singleStep(100000) // satoshis
29 : {
30 0 : setAlignment(Qt::AlignRight);
31 :
32 0 : connect(lineEdit(), SIGNAL(textEdited(QString)), this, SIGNAL(valueChanged()));
33 0 : }
34 :
35 0 : QValidator::State validate(QString &text, int &pos) const
36 : {
37 0 : if(text.isEmpty())
38 : return QValidator::Intermediate;
39 0 : bool valid = false;
40 0 : parse(text, &valid);
41 : /* Make sure we return Intermediate so that fixup() is called on defocus */
42 0 : return valid ? QValidator::Intermediate : QValidator::Invalid;
43 : }
44 :
45 0 : void fixup(QString &input) const
46 : {
47 0 : bool valid = false;
48 0 : CAmount val = parse(input, &valid);
49 0 : if(valid)
50 : {
51 0 : input = BitcoinUnits::format(currentUnit, val, false, BitcoinUnits::separatorAlways);
52 0 : lineEdit()->setText(input);
53 : }
54 0 : }
55 :
56 0 : CAmount value(bool *valid_out=0) const
57 : {
58 0 : return parse(text(), valid_out);
59 : }
60 :
61 0 : void setValue(const CAmount& value)
62 : {
63 0 : lineEdit()->setText(BitcoinUnits::format(currentUnit, value, false, BitcoinUnits::separatorAlways));
64 : Q_EMIT valueChanged();
65 0 : }
66 :
67 0 : void stepBy(int steps)
68 : {
69 0 : bool valid = false;
70 0 : CAmount val = value(&valid);
71 0 : val = val + steps * singleStep;
72 0 : val = qMin(qMax(val, CAmount(0)), BitcoinUnits::maxMoney());
73 0 : setValue(val);
74 0 : }
75 :
76 0 : void setDisplayUnit(int unit)
77 : {
78 0 : bool valid = false;
79 0 : CAmount val = value(&valid);
80 :
81 0 : currentUnit = unit;
82 :
83 0 : if(valid)
84 0 : setValue(val);
85 : else
86 0 : clear();
87 0 : }
88 :
89 0 : void setSingleStep(const CAmount& step)
90 : {
91 0 : singleStep = step;
92 0 : }
93 :
94 0 : QSize minimumSizeHint() const
95 : {
96 0 : if(cachedMinimumSizeHint.isEmpty())
97 : {
98 0 : ensurePolished();
99 :
100 0 : const QFontMetrics fm(fontMetrics());
101 0 : int h = lineEdit()->minimumSizeHint().height();
102 0 : int w = fm.width(BitcoinUnits::format(BitcoinUnits::BTC, BitcoinUnits::maxMoney(), false, BitcoinUnits::separatorAlways));
103 0 : w += 2; // cursor blinking space
104 :
105 0 : QStyleOptionSpinBox opt;
106 0 : initStyleOption(&opt);
107 : QSize hint(w, h);
108 : QSize extra(35, 6);
109 0 : opt.rect.setSize(hint + extra);
110 0 : extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &opt,
111 0 : QStyle::SC_SpinBoxEditField, this).size();
112 : // get closer to final result by repeating the calculation
113 0 : opt.rect.setSize(hint + extra);
114 0 : extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &opt,
115 0 : QStyle::SC_SpinBoxEditField, this).size();
116 : hint += extra;
117 : hint.setHeight(h);
118 :
119 0 : opt.rect = rect();
120 :
121 0 : cachedMinimumSizeHint = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this)
122 0 : .expandedTo(QApplication::globalStrut());
123 : }
124 0 : return cachedMinimumSizeHint;
125 : }
126 :
127 : private:
128 : int currentUnit;
129 : CAmount singleStep;
130 : mutable QSize cachedMinimumSizeHint;
131 :
132 : /**
133 : * Parse a string into a number of base monetary units and
134 : * return validity.
135 : * @note Must return 0 if !valid.
136 : */
137 0 : CAmount parse(const QString &text, bool *valid_out=0) const
138 : {
139 0 : CAmount val = 0;
140 0 : bool valid = BitcoinUnits::parse(currentUnit, text, &val);
141 0 : if(valid)
142 : {
143 0 : if(val < 0 || val > BitcoinUnits::maxMoney())
144 0 : valid = false;
145 : }
146 0 : if(valid_out)
147 0 : *valid_out = valid;
148 0 : return valid ? val : 0;
149 : }
150 :
151 : protected:
152 0 : bool event(QEvent *event)
153 : {
154 0 : if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease)
155 : {
156 0 : QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
157 0 : if (keyEvent->key() == Qt::Key_Comma)
158 : {
159 : // Translate a comma into a period
160 0 : QKeyEvent periodKeyEvent(event->type(), Qt::Key_Period, keyEvent->modifiers(), ".", keyEvent->isAutoRepeat(), keyEvent->count());
161 0 : return QAbstractSpinBox::event(&periodKeyEvent);
162 : }
163 : }
164 0 : return QAbstractSpinBox::event(event);
165 : }
166 :
167 0 : StepEnabled stepEnabled() const
168 : {
169 0 : if (isReadOnly()) // Disable steps when AmountSpinBox is read-only
170 : return StepNone;
171 0 : if (text().isEmpty()) // Allow step-up with empty field
172 : return StepUpEnabled;
173 :
174 : StepEnabled rv = 0;
175 0 : bool valid = false;
176 0 : CAmount val = value(&valid);
177 0 : if(valid)
178 : {
179 0 : if(val > 0)
180 : rv |= StepDownEnabled;
181 0 : if(val < BitcoinUnits::maxMoney())
182 : rv |= StepUpEnabled;
183 : }
184 : return rv;
185 : }
186 :
187 : Q_SIGNALS:
188 : void valueChanged();
189 : };
190 :
191 : #include "bitcoinamountfield.moc"
192 :
193 0 : BitcoinAmountField::BitcoinAmountField(QWidget *parent) :
194 : QWidget(parent),
195 0 : amount(0)
196 : {
197 0 : amount = new AmountSpinBox(this);
198 0 : amount->setLocale(QLocale::c());
199 0 : amount->installEventFilter(this);
200 0 : amount->setMaximumWidth(170);
201 :
202 0 : QHBoxLayout *layout = new QHBoxLayout(this);
203 0 : layout->addWidget(amount);
204 0 : unit = new QValueComboBox(this);
205 0 : unit->setModel(new BitcoinUnits(this));
206 0 : layout->addWidget(unit);
207 0 : layout->addStretch(1);
208 0 : layout->setContentsMargins(0,0,0,0);
209 :
210 0 : setLayout(layout);
211 :
212 0 : setFocusPolicy(Qt::TabFocus);
213 0 : setFocusProxy(amount);
214 :
215 : // If one if the widgets changes, the combined content changes as well
216 0 : connect(amount, SIGNAL(valueChanged()), this, SIGNAL(valueChanged()));
217 0 : connect(unit, SIGNAL(currentIndexChanged(int)), this, SLOT(unitChanged(int)));
218 :
219 : // Set default based on configuration
220 0 : unitChanged(unit->currentIndex());
221 0 : }
222 :
223 0 : void BitcoinAmountField::clear()
224 : {
225 0 : amount->clear();
226 0 : unit->setCurrentIndex(0);
227 0 : }
228 :
229 0 : void BitcoinAmountField::setEnabled(bool fEnabled)
230 : {
231 0 : amount->setEnabled(fEnabled);
232 0 : unit->setEnabled(fEnabled);
233 0 : }
234 :
235 0 : bool BitcoinAmountField::validate()
236 : {
237 0 : bool valid = false;
238 : value(&valid);
239 0 : setValid(valid);
240 0 : return valid;
241 : }
242 :
243 0 : void BitcoinAmountField::setValid(bool valid)
244 : {
245 0 : if (valid)
246 0 : amount->setStyleSheet("");
247 : else
248 0 : amount->setStyleSheet(STYLE_INVALID);
249 0 : }
250 :
251 0 : bool BitcoinAmountField::eventFilter(QObject *object, QEvent *event)
252 : {
253 0 : if (event->type() == QEvent::FocusIn)
254 : {
255 : // Clear invalid flag on focus
256 0 : setValid(true);
257 : }
258 0 : return QWidget::eventFilter(object, event);
259 : }
260 :
261 0 : QWidget *BitcoinAmountField::setupTabChain(QWidget *prev)
262 : {
263 0 : QWidget::setTabOrder(prev, amount);
264 0 : QWidget::setTabOrder(amount, unit);
265 0 : return unit;
266 : }
267 :
268 0 : CAmount BitcoinAmountField::value(bool *valid_out) const
269 : {
270 0 : return amount->value(valid_out);
271 : }
272 :
273 0 : void BitcoinAmountField::setValue(const CAmount& value)
274 : {
275 0 : amount->setValue(value);
276 0 : }
277 :
278 0 : void BitcoinAmountField::setReadOnly(bool fReadOnly)
279 : {
280 0 : amount->setReadOnly(fReadOnly);
281 0 : }
282 :
283 0 : void BitcoinAmountField::unitChanged(int idx)
284 : {
285 : // Use description tooltip for current unit for the combobox
286 0 : unit->setToolTip(unit->itemData(idx, Qt::ToolTipRole).toString());
287 :
288 : // Determine new unit ID
289 0 : int newUnit = unit->itemData(idx, BitcoinUnits::UnitRole).toInt();
290 :
291 0 : amount->setDisplayUnit(newUnit);
292 0 : }
293 :
294 0 : void BitcoinAmountField::setDisplayUnit(int newUnit)
295 : {
296 0 : unit->setValue(newUnit);
297 0 : }
298 :
299 0 : void BitcoinAmountField::setSingleStep(const CAmount& step)
300 : {
301 0 : amount->setSingleStep(step);
302 0 : }
|