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