Merge pull request #4167

7149499 Add comments re BitcoinUnits::formatWithUnit/formatHtmlWithUnit (Roy Badami)
f7d70c6 Remove unused fAlign argument from BitcoinUnits::format and friends (Roy Badami)
2e4fee2 Show bitcoin quantities with full precision, even in the presence of trailing zeros (Roy Badami)
7007402 Implement SI-style (thin space) thoudands separator (Roy Badami)
This commit is contained in:
Wladimir J. van der Laan 2014-07-18 13:49:46 +02:00
commit 40d2d69223
No known key found for this signature in database
GPG Key ID: 74810B012346C9A6
10 changed files with 202 additions and 47 deletions

View File

@ -14,6 +14,51 @@
#include <QKeyEvent> #include <QKeyEvent>
#include <qmath.h> // for qPow() #include <qmath.h> // for qPow()
// QDoubleSpinBox that shows SI-style thin space thousands separators
class AmountSpinBox: public QDoubleSpinBox
{
public:
explicit AmountSpinBox(QWidget *parent):
QDoubleSpinBox(parent)
{
}
QString textFromValue(double value) const
{
QStringList parts = QDoubleSpinBox::textFromValue(value).split(".");
QString quotient_str = parts[0];
QString remainder_str;
if(parts.size() > 1)
remainder_str = parts[1];
// Code duplication between here and BitcoinUnits::format
// TODO: Figure out how to share this code
QChar thin_sp(THIN_SP_CP);
int q_size = quotient_str.size();
if (q_size > 4)
for (int i = 3; i < q_size; i += 3)
quotient_str.insert(q_size - i, thin_sp);
int r_size = remainder_str.size();
if (r_size > 4)
for (int i = 3, adj = 0; i < r_size; i += 3, adj++)
remainder_str.insert(i + adj, thin_sp);
if(remainder_str.isEmpty())
return quotient_str;
else
return quotient_str + QString(".") + remainder_str;
}
QValidator::State validate (QString &text, int &pos) const
{
QString s(BitcoinUnits::removeSpaces(text));
return QDoubleSpinBox::validate(s, pos);
}
double valueFromText(const QString& text) const
{
return QDoubleSpinBox::valueFromText(BitcoinUnits::removeSpaces(text));
}
};
BitcoinAmountField::BitcoinAmountField(QWidget *parent) : BitcoinAmountField::BitcoinAmountField(QWidget *parent) :
QWidget(parent), QWidget(parent),
amount(0), amount(0),
@ -21,7 +66,7 @@ BitcoinAmountField::BitcoinAmountField(QWidget *parent) :
{ {
nSingleStep = 100000; // satoshis nSingleStep = 100000; // satoshis
amount = new QDoubleSpinBox(this); amount = new AmountSpinBox(this);
amount->setLocale(QLocale::c()); amount->setLocale(QLocale::c());
amount->installEventFilter(this); amount->installEventFilter(this);
amount->setMaximumWidth(170); amount->setMaximumWidth(170);
@ -52,7 +97,7 @@ void BitcoinAmountField::setText(const QString &text)
if (text.isEmpty()) if (text.isEmpty())
amount->clear(); amount->clear();
else else
amount->setValue(text.toDouble()); amount->setValue(BitcoinUnits::removeSpaces(text).toDouble());
} }
void BitcoinAmountField::clear() void BitcoinAmountField::clear()

View File

@ -61,8 +61,8 @@ QString BitcoinUnits::description(int unit)
switch(unit) switch(unit)
{ {
case BTC: return QString("Bitcoins"); case BTC: return QString("Bitcoins");
case mBTC: return QString("Milli-Bitcoins (1 / 1,000)"); case mBTC: return QString("Milli-Bitcoins (1 / 1" THIN_SP_UTF8 "000)");
case uBTC: return QString("Micro-Bitcoins (1 / 1,000,000)"); case uBTC: return QString("Micro-Bitcoins (1 / 1" THIN_SP_UTF8 "000" THIN_SP_UTF8 "000)");
default: return QString("???"); default: return QString("???");
} }
} }
@ -111,7 +111,7 @@ int BitcoinUnits::decimals(int unit)
} }
} }
QString BitcoinUnits::format(int unit, qint64 n, bool fPlus) QString BitcoinUnits::format(int unit, qint64 n, bool fPlus, SeparatorStyle separators)
{ {
// Note: not using straight sprintf here because we do NOT want // Note: not using straight sprintf here because we do NOT want
// localized number formatting. // localized number formatting.
@ -125,11 +125,20 @@ QString BitcoinUnits::format(int unit, qint64 n, bool fPlus)
QString quotient_str = QString::number(quotient); QString quotient_str = QString::number(quotient);
QString remainder_str = QString::number(remainder).rightJustified(num_decimals, '0'); QString remainder_str = QString::number(remainder).rightJustified(num_decimals, '0');
// Right-trim excess zeros after the decimal point // Use SI-stule separators as these are locale indendent and can't be
int nTrim = 0; // confused with the decimal marker. Rule is to use a thin space every
for (int i = remainder_str.size()-1; i>=2 && (remainder_str.at(i) == '0'); --i) // three digits on *both* sides of the decimal point - but only if there
++nTrim; // are five or more digits
remainder_str.chop(nTrim); QChar thin_sp(THIN_SP_CP);
int q_size = quotient_str.size();
if (separators == separatorAlways || (separators == separatorStandard && q_size > 4))
for (int i = 3; i < q_size; i += 3)
quotient_str.insert(q_size - i, thin_sp);
int r_size = remainder_str.size();
if (separators == separatorAlways || (separators == separatorStandard && r_size > 4))
for (int i = 3, adj = 0; i < r_size ; i += 3, adj++)
remainder_str.insert(i + adj, thin_sp);
if (n < 0) if (n < 0)
quotient_str.insert(0, '-'); quotient_str.insert(0, '-');
@ -138,17 +147,43 @@ QString BitcoinUnits::format(int unit, qint64 n, bool fPlus)
return quotient_str + QString(".") + remainder_str; return quotient_str + QString(".") + remainder_str;
} }
QString BitcoinUnits::formatWithUnit(int unit, qint64 amount, bool plussign)
// TODO: Review all remaining calls to BitcoinUnits::formatWithUnit to
// TODO: determine whether the output is used in a plain text context
// TODO: or an HTML context (and replace with
// TODO: BtcoinUnits::formatHtmlWithUnit in the latter case). Hopefully
// TODO: there aren't instances where the result could be used in
// TODO: either context.
// NOTE: Using formatWithUnit in an HTML context risks wrapping
// quantities at the thousands separator. More subtly, it also results
// in a standard space rather than a thin space, due to a bug in Qt's
// XML whitespace canonicalisation
//
// Please take care to use formatHtmlWithUnit instead, when
// appropriate.
QString BitcoinUnits::formatWithUnit(int unit, qint64 amount, bool plussign, SeparatorStyle separators)
{ {
return format(unit, amount, plussign) + QString(" ") + name(unit); return format(unit, amount, plussign, separators) + QString(" ") + name(unit);
} }
QString BitcoinUnits::formatHtmlWithUnit(int unit, qint64 amount, bool plussign, SeparatorStyle separators)
{
QString str(formatWithUnit(unit, amount, plussign, separators));
str.replace(QChar(THIN_SP_CP), QString(THIN_SP_HTML));
return QString("<span style='white-space: nowrap;'>%1</span>").arg(str);
}
bool BitcoinUnits::parse(int unit, const QString &value, qint64 *val_out) bool BitcoinUnits::parse(int unit, const QString &value, qint64 *val_out)
{ {
if(!valid(unit) || value.isEmpty()) if(!valid(unit) || value.isEmpty())
return false; // Refuse to parse invalid unit or empty string return false; // Refuse to parse invalid unit or empty string
int num_decimals = decimals(unit); int num_decimals = decimals(unit);
QStringList parts = value.split(".");
// Ignore spaces and thin spaces when parsing
QStringList parts = removeSpaces(value).split(".");
if(parts.size() > 2) if(parts.size() > 2)
{ {

View File

@ -8,6 +8,37 @@
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QString> #include <QString>
// U+2009 THIN SPACE = UTF-8 E2 80 89
#define REAL_THIN_SP_CP 0x2009
#define REAL_THIN_SP_UTF8 "\xE2\x80\x89"
#define REAL_THIN_SP_HTML "&thinsp;"
// U+200A HAIR SPACE = UTF-8 E2 80 8A
#define HAIR_SP_CP 0x200A
#define HAIR_SP_UTF8 "\xE2\x80\x8A"
#define HAIR_SP_HTML "&#8202;"
// U+2006 SIX-PER-EM SPACE = UTF-8 E2 80 86
#define SIXPEREM_SP_CP 0x2006
#define SIXPEREM_SP_UTF8 "\xE2\x80\x86"
#define SIXPEREM_SP_HTML "&#8198;"
// U+2007 FIGURE SPACE = UTF-8 E2 80 87
#define FIGURE_SP_CP 0x2007
#define FIGURE_SP_UTF8 "\xE2\x80\x87"
#define FIGURE_SP_HTML "&#8199;"
// QMessageBox seems to have a bug whereby it doesn't display thin/hair spaces
// correctly. Workaround is to display a space in a small font. If you
// change this, please test that it doesn't cause the parent span to start
// wrapping.
#define HTML_HACK_SP "<span style='white-space: nowrap; font-size: 6pt'> </span>"
// Define THIN_SP_* variables to be our preferred type of thin space
#define THIN_SP_CP REAL_THIN_SP_CP
#define THIN_SP_UTF8 REAL_THIN_SP_UTF8
#define THIN_SP_HTML HTML_HACK_SP
/** Bitcoin unit definitions. Encapsulates parsing and formatting /** Bitcoin unit definitions. Encapsulates parsing and formatting
and serves as list model for drop-down selection boxes. and serves as list model for drop-down selection boxes.
*/ */
@ -28,6 +59,13 @@ public:
uBTC uBTC
}; };
enum SeparatorStyle
{
separatorNever,
separatorStandard,
separatorAlways
};
//! @name Static API //! @name Static API
//! Unit conversion and formatting //! Unit conversion and formatting
///@{ ///@{
@ -51,9 +89,10 @@ public:
//! Number of decimals left //! Number of decimals left
static int decimals(int unit); static int decimals(int unit);
//! Format as string //! Format as string
static QString format(int unit, qint64 amount, bool plussign=false); static QString format(int unit, qint64 amount, bool plussign=false, SeparatorStyle separators=separatorStandard);
//! Format as string (with unit) //! Format as string (with unit)
static QString formatWithUnit(int unit, qint64 amount, bool plussign=false); static QString formatWithUnit(int unit, qint64 amount, bool plussign=false, SeparatorStyle separators=separatorStandard);
static QString formatHtmlWithUnit(int unit, qint64 amount, bool plussign=false, SeparatorStyle separators=separatorStandard);
//! Parse string to coin amount //! Parse string to coin amount
static bool parse(int unit, const QString &value, qint64 *val_out); static bool parse(int unit, const QString &value, qint64 *val_out);
//! Gets title for amount column including current display unit if optionsModel reference available */ //! Gets title for amount column including current display unit if optionsModel reference available */
@ -71,6 +110,16 @@ public:
QVariant data(const QModelIndex &index, int role) const; QVariant data(const QModelIndex &index, int role) const;
///@} ///@}
static QString removeSpaces(QString text)
{
text.remove(' ');
text.remove(QChar(THIN_SP_CP));
#if (THIN_SP_CP != REAL_THIN_SP_CP)
text.remove(QChar(REAL_THIN_SP_CP));
#endif
return text;
}
private: private:
QList<BitcoinUnits::Unit> unitlist; QList<BitcoinUnits::Unit> unitlist;
}; };

View File

@ -72,7 +72,7 @@ public:
foreground = option.palette.color(QPalette::Text); foreground = option.palette.color(QPalette::Text);
} }
painter->setPen(foreground); painter->setPen(foreground);
QString amountText = BitcoinUnits::formatWithUnit(unit, amount, true); QString amountText = BitcoinUnits::formatWithUnit(unit, amount, true, BitcoinUnits::separatorAlways);
if(!confirmed) if(!confirmed)
{ {
amountText = QString("[") + amountText + QString("]"); amountText = QString("[") + amountText + QString("]");
@ -147,14 +147,14 @@ void OverviewPage::setBalance(qint64 balance, qint64 unconfirmedBalance, qint64
currentWatchOnlyBalance = watchOnlyBalance; currentWatchOnlyBalance = watchOnlyBalance;
currentWatchUnconfBalance = watchUnconfBalance; currentWatchUnconfBalance = watchUnconfBalance;
currentWatchImmatureBalance = watchImmatureBalance; currentWatchImmatureBalance = watchImmatureBalance;
ui->labelBalance->setText(BitcoinUnits::formatWithUnit(unit, balance)); ui->labelBalance->setText(BitcoinUnits::formatWithUnit(unit, balance, false, BitcoinUnits::separatorAlways));
ui->labelUnconfirmed->setText(BitcoinUnits::formatWithUnit(unit, unconfirmedBalance)); ui->labelUnconfirmed->setText(BitcoinUnits::formatWithUnit(unit, unconfirmedBalance, false, BitcoinUnits::separatorAlways));
ui->labelImmature->setText(BitcoinUnits::formatWithUnit(unit, immatureBalance)); ui->labelImmature->setText(BitcoinUnits::formatWithUnit(unit, immatureBalance, false, BitcoinUnits::separatorAlways));
ui->labelTotal->setText(BitcoinUnits::formatWithUnit(unit, balance + unconfirmedBalance + immatureBalance)); ui->labelTotal->setText(BitcoinUnits::formatWithUnit(unit, balance + unconfirmedBalance + immatureBalance, false, BitcoinUnits::separatorAlways));
ui->labelWatchAvailable->setText(BitcoinUnits::formatWithUnit(unit, watchOnlyBalance)); ui->labelWatchAvailable->setText(BitcoinUnits::formatWithUnit(unit, watchOnlyBalance, false, BitcoinUnits::separatorAlways));
ui->labelWatchPending->setText(BitcoinUnits::formatWithUnit(unit, watchUnconfBalance)); ui->labelWatchPending->setText(BitcoinUnits::formatWithUnit(unit, watchUnconfBalance, false, BitcoinUnits::separatorAlways));
ui->labelWatchImmature->setText(BitcoinUnits::formatWithUnit(unit, watchImmatureBalance)); ui->labelWatchImmature->setText(BitcoinUnits::formatWithUnit(unit, watchImmatureBalance, false, BitcoinUnits::separatorAlways));
ui->labelWatchTotal->setText(BitcoinUnits::formatWithUnit(unit, watchOnlyBalance + watchUnconfBalance + watchImmatureBalance)); ui->labelWatchTotal->setText(BitcoinUnits::formatWithUnit(unit, watchOnlyBalance + watchUnconfBalance + watchImmatureBalance, false, BitcoinUnits::separatorAlways));
// only show immature (newly mined) balance if it's non-zero, so as not to complicate things // only show immature (newly mined) balance if it's non-zero, so as not to complicate things
// for the non-mining users // for the non-mining users

View File

@ -143,7 +143,7 @@ void SendCoinsDialog::on_sendButton_clicked()
foreach(const SendCoinsRecipient &rcp, recipients) foreach(const SendCoinsRecipient &rcp, recipients)
{ {
// generate bold amount string // generate bold amount string
QString amount = "<b>" + BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount); QString amount = "<b>" + BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
amount.append("</b>"); amount.append("</b>");
// generate monospace address string // generate monospace address string
QString address = "<span style='font-family: monospace;'>" + rcp.address; QString address = "<span style='font-family: monospace;'>" + rcp.address;
@ -211,7 +211,7 @@ void SendCoinsDialog::on_sendButton_clicked()
{ {
// append fee string if a fee is required // append fee string if a fee is required
questionString.append("<hr /><span style='color:#aa0000;'>"); questionString.append("<hr /><span style='color:#aa0000;'>");
questionString.append(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee)); questionString.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee));
questionString.append("</span> "); questionString.append("</span> ");
questionString.append(tr("added as transaction fee")); questionString.append(tr("added as transaction fee"));
} }
@ -223,10 +223,10 @@ void SendCoinsDialog::on_sendButton_clicked()
foreach(BitcoinUnits::Unit u, BitcoinUnits::availableUnits()) foreach(BitcoinUnits::Unit u, BitcoinUnits::availableUnits())
{ {
if(u != model->getOptionsModel()->getDisplayUnit()) if(u != model->getOptionsModel()->getDisplayUnit())
alternativeUnits.append(BitcoinUnits::formatWithUnit(u, totalAmount)); alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
} }
questionString.append(tr("Total Amount %1 (= %2)") questionString.append(tr("Total Amount %1 (= %2)")
.arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), totalAmount)) .arg(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), totalAmount))
.arg(alternativeUnits.join(" " + tr("or") + " "))); .arg(alternativeUnits.join(" " + tr("or") + " ")));
QMessageBox::StandardButton retval = QMessageBox::question(this, tr("Confirm send coins"), QMessageBox::StandardButton retval = QMessageBox::question(this, tr("Confirm send coins"),

View File

@ -136,7 +136,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
nUnmatured += wallet->GetCredit(txout, ISMINE_ALL); nUnmatured += wallet->GetCredit(txout, ISMINE_ALL);
strHTML += "<b>" + tr("Credit") + ":</b> "; strHTML += "<b>" + tr("Credit") + ":</b> ";
if (wtx.IsInMainChain()) if (wtx.IsInMainChain())
strHTML += BitcoinUnits::formatWithUnit(unit, nUnmatured)+ " (" + tr("matures in %n more block(s)", "", wtx.GetBlocksToMaturity()) + ")"; strHTML += BitcoinUnits::formatHtmlWithUnit(unit, nUnmatured)+ " (" + tr("matures in %n more block(s)", "", wtx.GetBlocksToMaturity()) + ")";
else else
strHTML += "(" + tr("not accepted") + ")"; strHTML += "(" + tr("not accepted") + ")";
strHTML += "<br>"; strHTML += "<br>";
@ -146,7 +146,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
// //
// Credit // Credit
// //
strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, nNet) + "<br>"; strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nNet) + "<br>";
} }
else else
{ {
@ -197,9 +197,9 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
} }
} }
strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, -txout.nValue) + "<br>"; strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -txout.nValue) + "<br>";
if(toSelf) if(toSelf)
strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, txout.nValue) + "<br>"; strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, txout.nValue) + "<br>";
} }
if (fAllToMe) if (fAllToMe)
@ -207,13 +207,13 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
// Payment to self // Payment to self
int64_t nChange = wtx.GetChange(); int64_t nChange = wtx.GetChange();
int64_t nValue = nCredit - nChange; int64_t nValue = nCredit - nChange;
strHTML += "<b>" + tr("Total debit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, -nValue) + "<br>"; strHTML += "<b>" + tr("Total debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -nValue) + "<br>";
strHTML += "<b>" + tr("Total credit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, nValue) + "<br>"; strHTML += "<b>" + tr("Total credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nValue) + "<br>";
} }
int64_t nTxFee = nDebit - wtx.GetValueOut(); int64_t nTxFee = nDebit - wtx.GetValueOut();
if (nTxFee > 0) if (nTxFee > 0)
strHTML += "<b>" + tr("Transaction fee") + ":</b> " + BitcoinUnits::formatWithUnit(unit, -nTxFee) + "<br>"; strHTML += "<b>" + tr("Transaction fee") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -nTxFee) + "<br>";
} }
else else
{ {
@ -222,14 +222,14 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
// //
BOOST_FOREACH(const CTxIn& txin, wtx.vin) BOOST_FOREACH(const CTxIn& txin, wtx.vin)
if (wallet->IsMine(txin)) if (wallet->IsMine(txin))
strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, -wallet->GetDebit(txin, ISMINE_ALL)) + "<br>"; strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -wallet->GetDebit(txin, ISMINE_ALL)) + "<br>";
BOOST_FOREACH(const CTxOut& txout, wtx.vout) BOOST_FOREACH(const CTxOut& txout, wtx.vout)
if (wallet->IsMine(txout)) if (wallet->IsMine(txout))
strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, wallet->GetCredit(txout, ISMINE_ALL)) + "<br>"; strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wallet->GetCredit(txout, ISMINE_ALL)) + "<br>";
} }
} }
strHTML += "<b>" + tr("Net amount") + ":</b> " + BitcoinUnits::formatWithUnit(unit, nNet, true) + "<br>"; strHTML += "<b>" + tr("Net amount") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nNet, true) + "<br>";
// //
// Message // Message
@ -275,10 +275,10 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
strHTML += "<hr><br>" + tr("Debug information") + "<br><br>"; strHTML += "<hr><br>" + tr("Debug information") + "<br><br>";
BOOST_FOREACH(const CTxIn& txin, wtx.vin) BOOST_FOREACH(const CTxIn& txin, wtx.vin)
if(wallet->IsMine(txin)) if(wallet->IsMine(txin))
strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, -wallet->GetDebit(txin, ISMINE_ALL)) + "<br>"; strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -wallet->GetDebit(txin, ISMINE_ALL)) + "<br>";
BOOST_FOREACH(const CTxOut& txout, wtx.vout) BOOST_FOREACH(const CTxOut& txout, wtx.vout)
if(wallet->IsMine(txout)) if(wallet->IsMine(txout))
strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, wallet->GetCredit(txout, ISMINE_ALL)) + "<br>"; strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wallet->GetCredit(txout, ISMINE_ALL)) + "<br>";
strHTML += "<br><b>" + tr("Transaction") + ":</b><br>"; strHTML += "<br><b>" + tr("Transaction") + ":</b><br>";
strHTML += GUIUtil::HtmlEscape(wtx.ToString(), true); strHTML += GUIUtil::HtmlEscape(wtx.ToString(), true);
@ -304,7 +304,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[address].name) + " "; strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[address].name) + " ";
strHTML += QString::fromStdString(CBitcoinAddress(address).ToString()); strHTML += QString::fromStdString(CBitcoinAddress(address).ToString());
} }
strHTML = strHTML + " " + tr("Amount") + "=" + BitcoinUnits::formatWithUnit(unit, vout.nValue); strHTML = strHTML + " " + tr("Amount") + "=" + BitcoinUnits::formatHtmlWithUnit(unit, vout.nValue);
strHTML = strHTML + " IsMine=" + (wallet->IsMine(vout) & ISMINE_SPENDABLE ? tr("true") : tr("false")) + "</li>"; strHTML = strHTML + " IsMine=" + (wallet->IsMine(vout) & ISMINE_SPENDABLE ? tr("true") : tr("false")) + "</li>";
strHTML = strHTML + " IsWatchOnly=" + (wallet->IsMine(vout) & ISMINE_WATCH_ONLY ? tr("true") : tr("false")) + "</li>"; strHTML = strHTML + " IsWatchOnly=" + (wallet->IsMine(vout) & ISMINE_WATCH_ONLY ? tr("true") : tr("false")) + "</li>";
} }

View File

@ -5,7 +5,6 @@
#include "transactiontablemodel.h" #include "transactiontablemodel.h"
#include "addresstablemodel.h" #include "addresstablemodel.h"
#include "bitcoinunits.h"
#include "guiconstants.h" #include "guiconstants.h"
#include "guiutil.h" #include "guiutil.h"
#include "optionsmodel.h" #include "optionsmodel.h"
@ -436,9 +435,9 @@ QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const
return QVariant(); return QVariant();
} }
QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed, BitcoinUnits::SeparatorStyle separators) const
{ {
QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit); QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit, false, separators);
if(showUnconfirmed) if(showUnconfirmed)
{ {
if(!wtx->status.countsForBalance) if(!wtx->status.countsForBalance)
@ -523,7 +522,7 @@ QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
case ToAddress: case ToAddress:
return formatTxToAddress(rec, false); return formatTxToAddress(rec, false);
case Amount: case Amount:
return formatTxAmount(rec); return formatTxAmount(rec, true, BitcoinUnits::separatorAlways);
} }
break; break;
case Qt::EditRole: case Qt::EditRole:
@ -586,7 +585,8 @@ QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
case ConfirmedRole: case ConfirmedRole:
return rec->status.countsForBalance; return rec->status.countsForBalance;
case FormattedAmountRole: case FormattedAmountRole:
return formatTxAmount(rec, false); // Used for copy/export, so don't include separators
return formatTxAmount(rec, false, BitcoinUnits::separatorNever);
case StatusRole: case StatusRole:
return rec->status.status; return rec->status.status;
} }

View File

@ -8,6 +8,8 @@
#include <QAbstractTableModel> #include <QAbstractTableModel>
#include <QStringList> #include <QStringList>
#include "bitcoinunits.h"
class TransactionRecord; class TransactionRecord;
class TransactionTablePriv; class TransactionTablePriv;
class WalletModel; class WalletModel;
@ -78,7 +80,7 @@ private:
QString formatTxDate(const TransactionRecord *wtx) const; QString formatTxDate(const TransactionRecord *wtx) const;
QString formatTxType(const TransactionRecord *wtx) const; QString formatTxType(const TransactionRecord *wtx) const;
QString formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const; QString formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const;
QString formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed=true) const; QString formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed=true, BitcoinUnits::SeparatorStyle separators=BitcoinUnits::separatorStandard) const;
QString formatTooltip(const TransactionRecord *rec) const; QString formatTooltip(const TransactionRecord *rec) const;
QVariant txStatusDecoration(const TransactionRecord *wtx) const; QVariant txStatusDecoration(const TransactionRecord *wtx) const;
QVariant txAddressDecoration(const TransactionRecord *wtx) const; QVariant txAddressDecoration(const TransactionRecord *wtx) const;

View File

@ -123,6 +123,8 @@ TransactionView::TransactionView(QWidget *parent) :
view->setTabKeyNavigation(false); view->setTabKeyNavigation(false);
view->setContextMenuPolicy(Qt::CustomContextMenu); view->setContextMenuPolicy(Qt::CustomContextMenu);
view->installEventFilter(this);
transactionView = view; transactionView = view;
// Actions // Actions
@ -480,3 +482,22 @@ void TransactionView::resizeEvent(QResizeEvent* event)
QWidget::resizeEvent(event); QWidget::resizeEvent(event);
columnResizingFixer->stretchColumnWidth(TransactionTableModel::ToAddress); columnResizingFixer->stretchColumnWidth(TransactionTableModel::ToAddress);
} }
// Need to override default Ctrl+C action for amount as default behaviour is just to copy DisplayRole text
bool TransactionView::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::KeyPress)
{
QKeyEvent *ke = static_cast<QKeyEvent *>(event);
if (ke->key() == Qt::Key_C && ke->modifiers().testFlag(Qt::ControlModifier))
{
QModelIndex i = this->transactionView->currentIndex();
if (i.isValid() && i.column() == TransactionTableModel::Amount)
{
GUIUtil::setClipboard(i.data(TransactionTableModel::FormattedAmountRole).toString());
return true;
}
}
}
return QWidget::eventFilter(obj, event);
}

View File

@ -8,6 +8,7 @@
#include "guiutil.h" #include "guiutil.h"
#include <QWidget> #include <QWidget>
#include <QKeyEvent>
class TransactionFilterProxy; class TransactionFilterProxy;
class WalletModel; class WalletModel;
@ -78,6 +79,8 @@ private:
virtual void resizeEvent(QResizeEvent* event); virtual void resizeEvent(QResizeEvent* event);
bool eventFilter(QObject *obj, QEvent *event);
private slots: private slots:
void contextualMenu(const QPoint &); void contextualMenu(const QPoint &);
void dateRangeChanged(); void dateRangeChanged();