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