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
5 #include <qt/bitcoinunits.h>
6
7 #include <QStringList>
8
BitcoinUnits(QObject * parent)9 BitcoinUnits::BitcoinUnits(QObject *parent):
10 QAbstractListModel(parent),
11 unitlist(availableUnits())
12 {
13 }
14
availableUnits()15 QList<BitcoinUnits::Unit> BitcoinUnits::availableUnits()
16 {
17 QList<BitcoinUnits::Unit> unitlist;
18 unitlist.append(BTC);
19 unitlist.append(mBTC);
20 unitlist.append(uBTC);
21 unitlist.append(SAT);
22 return unitlist;
23 }
24
valid(int unit)25 bool BitcoinUnits::valid(int unit)
26 {
27 switch(unit)
28 {
29 case BTC:
30 case mBTC:
31 case uBTC:
32 case SAT:
33 return true;
34 default:
35 return false;
36 }
37 }
38
longName(int unit)39 QString BitcoinUnits::longName(int unit)
40 {
41 switch(unit)
42 {
43 case BTC: return QString("QTUM");
44 case mBTC: return QString("mQTUM");
45 case uBTC: return QString::fromUtf8("μQTUM (bits)");
46 case SAT: return QString("Satoshi (sat)");
47 default: return QString("???");
48 }
49 }
50
shortName(int unit)51 QString BitcoinUnits::shortName(int unit)
52 {
53 switch(unit)
54 {
55 case uBTC: return QString::fromUtf8("bits");
56 case SAT: return QString("sat");
57 default: return longName(unit);
58 }
59 }
60
description(int unit)61 QString BitcoinUnits::description(int unit)
62 {
63 switch(unit)
64 {
65 case BTC: return QString("Qtums");
66 case mBTC: return QString("Milli-Qtums (1 / 1" THIN_SP_UTF8 "000)");
67 case uBTC: return QString("Micro-Qtums (bits) (1 / 1" THIN_SP_UTF8 "000" THIN_SP_UTF8 "000)");
68 case SAT: return QString("Satoshi (sat) (1 / 100" THIN_SP_UTF8 "000" THIN_SP_UTF8 "000)");
69 default: return QString("???");
70 }
71 }
72
factor(int unit)73 qint64 BitcoinUnits::factor(int unit)
74 {
75 switch(unit)
76 {
77 case BTC: return 100000000;
78 case mBTC: return 100000;
79 case uBTC: return 100;
80 case SAT: return 1;
81 default: return 100000000;
82 }
83 }
84
tokenFactor(int decimalUnits)85 int256_t BitcoinUnits::tokenFactor(int decimalUnits)
86 {
87 if(decimalUnits == 0)
88 return 0;
89 int256_t factor = 1;
90 for(int i = 0; i < decimalUnits; i++){
91 factor *= 10;
92 }
93 return factor;
94 }
95
decimals(int unit)96 int BitcoinUnits::decimals(int unit)
97 {
98 switch(unit)
99 {
100 case BTC: return 8;
101 case mBTC: return 5;
102 case uBTC: return 2;
103 case SAT: return 0;
104 default: return 0;
105 }
106 }
107
format(int unit,const CAmount & nIn,bool fPlus,SeparatorStyle separators)108 QString BitcoinUnits::format(int unit, const CAmount& nIn, bool fPlus, SeparatorStyle separators)
109 {
110 // Note: not using straight sprintf here because we do NOT want
111 // localized number formatting.
112 if(!valid(unit))
113 return QString(); // Refuse to format invalid unit
114 qint64 n = (qint64)nIn;
115 qint64 coin = factor(unit);
116 int num_decimals = decimals(unit);
117 qint64 n_abs = (n > 0 ? n : -n);
118 qint64 quotient = n_abs / coin;
119 QString quotient_str = QString::number(quotient);
120
121 // Use SI-style thin space separators as these are locale independent and can't be
122 // confused with the decimal marker.
123 QChar thin_sp(THIN_SP_CP);
124 int q_size = quotient_str.size();
125 if (separators == separatorAlways || (separators == separatorStandard && q_size > 4))
126 for (int i = 3; i < q_size; i += 3)
127 quotient_str.insert(q_size - i, thin_sp);
128
129 if (n < 0)
130 quotient_str.insert(0, '-');
131 else if (fPlus && n > 0)
132 quotient_str.insert(0, '+');
133
134 if (num_decimals > 0) {
135 qint64 remainder = n_abs % coin;
136 QString remainder_str = QString::number(remainder).rightJustified(num_decimals, '0');
137 return quotient_str + QString(".") + remainder_str;
138 } else {
139 return quotient_str;
140 }
141 }
142
143
144 // NOTE: Using formatWithUnit in an HTML context risks wrapping
145 // quantities at the thousands separator. More subtly, it also results
146 // in a standard space rather than a thin space, due to a bug in Qt's
147 // XML whitespace canonicalisation
148 //
149 // Please take care to use formatHtmlWithUnit instead, when
150 // appropriate.
151
formatWithUnit(int unit,const CAmount & amount,bool plussign,SeparatorStyle separators)152 QString BitcoinUnits::formatWithUnit(int unit, const CAmount& amount, bool plussign, SeparatorStyle separators)
153 {
154 return format(unit, amount, plussign, separators) + QString(" ") + shortName(unit);
155 }
156
formatHtmlWithUnit(int unit,const CAmount & amount,bool plussign,SeparatorStyle separators)157 QString BitcoinUnits::formatHtmlWithUnit(int unit, const CAmount& amount, bool plussign, SeparatorStyle separators)
158 {
159 QString str(formatWithUnit(unit, amount, plussign, separators));
160 str.replace(QChar(THIN_SP_CP), QString(THIN_SP_HTML));
161 return QString("<span style='white-space: nowrap;'>%1</span>").arg(str);
162 }
163
164
parse(int unit,const QString & value,CAmount * val_out)165 bool BitcoinUnits::parse(int unit, const QString &value, CAmount *val_out)
166 {
167 if(!valid(unit) || value.isEmpty())
168 return false; // Refuse to parse invalid unit or empty string
169 int num_decimals = decimals(unit);
170
171 // Ignore spaces and thin spaces when parsing
172 QStringList parts = removeSpaces(value).split(".");
173
174 if(parts.size() > 2)
175 {
176 return false; // More than one dot
177 }
178 QString whole = parts[0];
179 QString decimals;
180
181 if(parts.size() > 1)
182 {
183 decimals = parts[1];
184 }
185 if(decimals.size() > num_decimals)
186 {
187 return false; // Exceeds max precision
188 }
189 bool ok = false;
190 QString str = whole + decimals.leftJustified(num_decimals, '0');
191
192 if(str.size() > 18)
193 {
194 return false; // Longer numbers will exceed 63 bits
195 }
196 CAmount retvalue(str.toLongLong(&ok));
197 if(val_out)
198 {
199 *val_out = retvalue;
200 }
201 return ok;
202 }
203
parseToken(int decimal_units,const QString & value,int256_t * val_out)204 bool BitcoinUnits::parseToken(int decimal_units, const QString &value, int256_t *val_out)
205 {
206 if(value.isEmpty())
207 return false; // Refuse to parse empty string
208
209 // Ignore spaces and thin spaces when parsing
210 QStringList parts = removeSpaces(value).split(".");
211
212 if(parts.size() > 2)
213 {
214 return false; // More than one dot
215 }
216 QString whole = parts[0];
217 QString decimals;
218
219 if(parts.size() > 1)
220 {
221 decimals = parts[1];
222 }
223 if(decimals.size() > decimal_units)
224 {
225 return false; // Exceeds max precision
226 }
227 bool ok = false;
228 QString str = whole + decimals.leftJustified(decimal_units, '0');
229
230 //remove leading zeros from str
231 while (str.startsWith('0'))
232 {
233 str.remove(0,1);
234 }
235
236 if(str.size() > 77)
237 {
238 return false; // Longer numbers will exceed 256 bits
239 }
240
241 int256_t retvalue;
242 try
243 {
244 retvalue = int256_t(str.toStdString());
245 ok = true;
246 }
247 catch(...)
248 {
249 ok = false;
250 }
251
252 if(val_out)
253 {
254 *val_out = retvalue;
255 }
256 return ok;
257 }
258
formatToken(int decimal_units,const int256_t & nIn,bool fPlus,SeparatorStyle separators)259 QString BitcoinUnits::formatToken(int decimal_units, const int256_t& nIn, bool fPlus, SeparatorStyle separators)
260 {
261 int256_t n = nIn;
262 int256_t n_abs = (n > 0 ? n : -n);
263 int256_t quotient;
264 int256_t remainder;
265 QString quotient_str;
266 QString remainder_str;
267 int256_t coin = tokenFactor(decimal_units);
268 if(coin != 0)
269 {
270 quotient = n_abs / coin;
271 remainder = n_abs % coin;
272 remainder_str = QString::fromStdString(remainder.str()).rightJustified(decimal_units, '0');
273 }
274 else
275 {
276 quotient = n_abs;
277 }
278 quotient_str = QString::fromStdString(quotient.str());
279
280 // Use SI-style thin space separators as these are locale independent and can't be
281 // confused with the decimal marker.
282 QChar thin_sp(THIN_SP_CP);
283 int q_size = quotient_str.size();
284 if (separators == separatorAlways || (separators == separatorStandard && q_size > 4))
285 for (int i = 3; i < q_size; i += 3)
286 quotient_str.insert(q_size - i, thin_sp);
287
288 if (n < 0)
289 quotient_str.insert(0, '-');
290 else if (fPlus && n > 0)
291 quotient_str.insert(0, '+');
292 if(remainder_str.size())
293 {
294 return quotient_str + QString(".") + remainder_str;
295 }
296 return quotient_str;
297 }
298
formatTokenWithUnit(const QString unit,int decimals,const int256_t & amount,bool plussign,BitcoinUnits::SeparatorStyle separators)299 QString BitcoinUnits::formatTokenWithUnit(const QString unit, int decimals, const int256_t &amount, bool plussign, BitcoinUnits::SeparatorStyle separators)
300 {
301 return formatToken(decimals, amount, plussign, separators) + " " + unit;
302 }
303
formatInt(const int64_t & nIn,bool fPlus,BitcoinUnits::SeparatorStyle separators)304 QString BitcoinUnits::formatInt(const int64_t &nIn, bool fPlus, BitcoinUnits::SeparatorStyle separators)
305 {
306 qint64 n = (qint64)nIn;
307 qint64 quotient = (n > 0 ? n : -n);
308 QString quotient_str = QString::number(quotient);
309
310 // Use SI-style thin space separators as these are locale independent and can't be
311 // confused with the decimal marker.
312 QChar thin_sp(THIN_SP_CP);
313 int q_size = quotient_str.size();
314 if (separators == separatorAlways || (separators == separatorStandard && q_size > 4))
315 for (int i = 3; i < q_size; i += 3)
316 quotient_str.insert(q_size - i, thin_sp);
317
318 if (n < 0)
319 quotient_str.insert(0, '-');
320 else if (fPlus && n > 0)
321 quotient_str.insert(0, '+');
322
323 return quotient_str;
324 }
325
getAmountColumnTitle(int unit)326 QString BitcoinUnits::getAmountColumnTitle(int unit)
327 {
328 QString amountTitle = QObject::tr("Amount");
329 if (BitcoinUnits::valid(unit))
330 {
331 amountTitle += " ("+BitcoinUnits::shortName(unit) + ")";
332 }
333 return amountTitle;
334 }
335
rowCount(const QModelIndex & parent) const336 int BitcoinUnits::rowCount(const QModelIndex &parent) const
337 {
338 Q_UNUSED(parent);
339 return unitlist.size();
340 }
341
data(const QModelIndex & index,int role) const342 QVariant BitcoinUnits::data(const QModelIndex &index, int role) const
343 {
344 int row = index.row();
345 if(row >= 0 && row < unitlist.size())
346 {
347 Unit unit = unitlist.at(row);
348 switch(role)
349 {
350 case Qt::EditRole:
351 case Qt::DisplayRole:
352 return QVariant(longName(unit));
353 case Qt::ToolTipRole:
354 return QVariant(description(unit));
355 case UnitRole:
356 return QVariant(static_cast<int>(unit));
357 }
358 }
359 return QVariant();
360 }
361
maxMoney()362 CAmount BitcoinUnits::maxMoney()
363 {
364 return MAX_MONEY;
365 }
366