diff --git a/src/qt/bitcoinamountfield.cpp b/src/qt/bitcoinamountfield.cpp index bd4328e96..586053213 100644 --- a/src/qt/bitcoinamountfield.cpp +++ b/src/qt/bitcoinamountfield.cpp @@ -1,293 +1,293 @@ // Copyright (c) 2011-2015 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "bitcoinamountfield.h" #include "bitcoinunits.h" #include "guiconstants.h" #include "qvaluecombobox.h" #include #include #include #include #include /** * QSpinBox that uses fixed-point numbers internally and uses our own * formatting/parsing functions. */ class AmountSpinBox : public QAbstractSpinBox { Q_OBJECT public: explicit AmountSpinBox(QWidget *parent) : QAbstractSpinBox(parent), currentUnit(BitcoinUnits::BCH), singleStep(100000 /* satoshis */) { setAlignment(Qt::AlignRight); connect(lineEdit(), SIGNAL(textEdited(QString)), this, SIGNAL(valueChanged())); } QValidator::State validate(QString &text, int &pos) const { if (text.isEmpty()) return QValidator::Intermediate; bool valid = false; parse(text, &valid); // Make sure we return Intermediate so that fixup() is called on // defocus. return valid ? QValidator::Intermediate : QValidator::Invalid; } void fixup(QString &input) const { bool valid = false; CAmount val = parse(input, &valid); if (valid) { input = BitcoinUnits::format(currentUnit, val, false, BitcoinUnits::separatorAlways); lineEdit()->setText(input); } } CAmount value(bool *valid_out = 0) const { return parse(text(), valid_out); } void setValue(const CAmount &value) { lineEdit()->setText(BitcoinUnits::format( currentUnit, value, false, BitcoinUnits::separatorAlways)); Q_EMIT valueChanged(); } void stepBy(int steps) { bool valid = false; - CAmount val = value(&valid); + Amount val(value(&valid)); val = val + steps * singleStep; - val = qMin(qMax(val, CAmount(0)), BitcoinUnits::maxMoney()); - setValue(val); + val = qMin(qMax(val, Amount(0)), BitcoinUnits::maxMoney()); + setValue(val.GetSatoshis()); } void setDisplayUnit(int unit) { bool valid = false; - CAmount val = value(&valid); + Amount val(value(&valid)); currentUnit = unit; if (valid) - setValue(val); + setValue(val.GetSatoshis()); else clear(); } void setSingleStep(const CAmount &step) { singleStep = step; } QSize minimumSizeHint() const { if (cachedMinimumSizeHint.isEmpty()) { ensurePolished(); const QFontMetrics fm(fontMetrics()); int h = lineEdit()->minimumSizeHint().height(); int w = fm.width(BitcoinUnits::format( BitcoinUnits::BCH, BitcoinUnits::maxMoney(), false, BitcoinUnits::separatorAlways)); // Cursor blinking space. w += 2; QStyleOptionSpinBox opt; initStyleOption(&opt); QSize hint(w, h); QSize extra(35, 6); opt.rect.setSize(hint + extra); extra += hint - style() ->subControlRect(QStyle::CC_SpinBox, &opt, QStyle::SC_SpinBoxEditField, this) .size(); // Get closer to final result by repeating the calculation. opt.rect.setSize(hint + extra); extra += hint - style() ->subControlRect(QStyle::CC_SpinBox, &opt, QStyle::SC_SpinBoxEditField, this) .size(); hint += extra; hint.setHeight(h); opt.rect = rect(); cachedMinimumSizeHint = style() ->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this) .expandedTo(QApplication::globalStrut()); } return cachedMinimumSizeHint; } private: int currentUnit; CAmount singleStep; mutable QSize cachedMinimumSizeHint; /** * Parse a string into a number of base monetary units and * return validity. * @note Must return 0 if !valid. */ CAmount parse(const QString &text, bool *valid_out = 0) const { - CAmount val = 0; + Amount val = 0; bool valid = BitcoinUnits::parse(currentUnit, text, &val); if (valid) { - if (val < 0 || val > BitcoinUnits::maxMoney()) { + if (val < Amount(0) || val > BitcoinUnits::maxMoney()) { valid = false; } } if (valid_out) { *valid_out = valid; } - return valid ? val : 0; + return valid ? val.GetSatoshis() : 0; } protected: bool event(QEvent *event) { if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { QKeyEvent *keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Comma) { // Translate a comma into a period. QKeyEvent periodKeyEvent( event->type(), Qt::Key_Period, keyEvent->modifiers(), ".", keyEvent->isAutoRepeat(), keyEvent->count()); return QAbstractSpinBox::event(&periodKeyEvent); } } return QAbstractSpinBox::event(event); } StepEnabled stepEnabled() const { if (isReadOnly()) { // Disable steps when AmountSpinBox is read-only. return StepNone; } if (text().isEmpty()) { // Allow step-up with empty field. return StepUpEnabled; } StepEnabled rv = 0; bool valid = false; CAmount val = value(&valid); if (valid) { if (val > 0) { rv |= StepDownEnabled; } if (val < BitcoinUnits::maxMoney()) { rv |= StepUpEnabled; } } return rv; } Q_SIGNALS: void valueChanged(); }; #include "bitcoinamountfield.moc" BitcoinAmountField::BitcoinAmountField(QWidget *parent) : QWidget(parent), amount(0) { amount = new AmountSpinBox(this); amount->setLocale(QLocale::c()); amount->installEventFilter(this); amount->setMaximumWidth(170); QHBoxLayout *layout = new QHBoxLayout(this); layout->addWidget(amount); unit = new QValueComboBox(this); unit->setModel(new BitcoinUnits(this)); layout->addWidget(unit); layout->addStretch(1); layout->setContentsMargins(0, 0, 0, 0); setLayout(layout); setFocusPolicy(Qt::TabFocus); setFocusProxy(amount); // If one if the widgets changes, the combined content changes as well connect(amount, SIGNAL(valueChanged()), this, SIGNAL(valueChanged())); connect(unit, SIGNAL(currentIndexChanged(int)), this, SLOT(unitChanged(int))); // Set default based on configuration unitChanged(unit->currentIndex()); } void BitcoinAmountField::clear() { amount->clear(); unit->setCurrentIndex(0); } void BitcoinAmountField::setEnabled(bool fEnabled) { amount->setEnabled(fEnabled); unit->setEnabled(fEnabled); } bool BitcoinAmountField::validate() { bool valid = false; value(&valid); setValid(valid); return valid; } void BitcoinAmountField::setValid(bool valid) { if (valid) { amount->setStyleSheet(""); } else { amount->setStyleSheet(STYLE_INVALID); } } bool BitcoinAmountField::eventFilter(QObject *object, QEvent *event) { if (event->type() == QEvent::FocusIn) { // Clear invalid flag on focus setValid(true); } return QWidget::eventFilter(object, event); } QWidget *BitcoinAmountField::setupTabChain(QWidget *prev) { QWidget::setTabOrder(prev, amount); QWidget::setTabOrder(amount, unit); return unit; } CAmount BitcoinAmountField::value(bool *valid_out) const { return amount->value(valid_out); } void BitcoinAmountField::setValue(const CAmount &value) { amount->setValue(value); } void BitcoinAmountField::setReadOnly(bool fReadOnly) { amount->setReadOnly(fReadOnly); } void BitcoinAmountField::unitChanged(int idx) { // Use description tooltip for current unit for the combobox unit->setToolTip(unit->itemData(idx, Qt::ToolTipRole).toString()); // Determine new unit ID int newUnit = unit->itemData(idx, BitcoinUnits::UnitRole).toInt(); amount->setDisplayUnit(newUnit); } void BitcoinAmountField::setDisplayUnit(int newUnit) { unit->setValue(newUnit); } void BitcoinAmountField::setSingleStep(const CAmount &step) { amount->setSingleStep(step); } diff --git a/src/qt/bitcoinunits.cpp b/src/qt/bitcoinunits.cpp index 11cbc965e..c03417bd1 100644 --- a/src/qt/bitcoinunits.cpp +++ b/src/qt/bitcoinunits.cpp @@ -1,216 +1,216 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "bitcoinunits.h" #include "primitives/transaction.h" #include BitcoinUnits::BitcoinUnits(QObject *parent) : QAbstractListModel(parent), unitlist(availableUnits()) {} QList BitcoinUnits::availableUnits() { QList unitlist; unitlist.append(BCH); unitlist.append(mBCH); unitlist.append(uBCH); return unitlist; } bool BitcoinUnits::valid(int unit) { switch (unit) { case BCH: case mBCH: case uBCH: return true; default: return false; } } QString BitcoinUnits::name(int unit) { switch (unit) { case BCH: return QString("BCH"); case mBCH: return QString("mBCH"); case uBCH: return QString::fromUtf8("μBCH"); default: return QString("???"); } } QString BitcoinUnits::description(int unit) { switch (unit) { case BCH: return QString("Bitcoins"); case mBCH: return QString("Milli-Bitcoins (1 / 1" THIN_SP_UTF8 "000)"); case uBCH: return QString("Micro-Bitcoins (1 / 1" THIN_SP_UTF8 "000" THIN_SP_UTF8 "000)"); default: return QString("???"); } } qint64 BitcoinUnits::factor(int unit) { switch (unit) { case BCH: return 100000000; case mBCH: return 100000; case uBCH: return 100; default: return 100000000; } } int BitcoinUnits::decimals(int unit) { switch (unit) { case BCH: return 8; case mBCH: return 5; case uBCH: return 2; default: return 0; } } -QString BitcoinUnits::format(int unit, const CAmount &nIn, bool fPlus, +QString BitcoinUnits::format(int unit, const Amount nIn, bool fPlus, SeparatorStyle separators) { // Note: not using straight sprintf here because we do NOT want // localized number formatting. if (!valid(unit)) { // Refuse to format invalid unit return QString(); } - qint64 n = (qint64)nIn; + qint64 n = (qint64)nIn.GetSatoshis(); qint64 coin = factor(unit); int num_decimals = decimals(unit); qint64 n_abs = (n > 0 ? n : -n); qint64 quotient = n_abs / coin; qint64 remainder = n_abs % coin; QString quotient_str = QString::number(quotient); QString remainder_str = QString::number(remainder).rightJustified(num_decimals, '0'); // Use SI-style thin space separators as these are locale independent and // can't be confused with the decimal marker. 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); } } if (n < 0) { quotient_str.insert(0, '-'); } else if (fPlus && n > 0) { quotient_str.insert(0, '+'); } return quotient_str + QString(".") + remainder_str; } // 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, const CAmount &amount, +QString BitcoinUnits::formatWithUnit(int unit, const Amount amount, bool plussign, SeparatorStyle separators) { return format(unit, amount, plussign, separators) + QString(" ") + name(unit); } -QString BitcoinUnits::formatHtmlWithUnit(int unit, const CAmount &amount, +QString BitcoinUnits::formatHtmlWithUnit(int unit, const Amount amount, bool plussign, SeparatorStyle separators) { QString str(formatWithUnit(unit, amount, plussign, separators)); str.replace(QChar(THIN_SP_CP), QString(THIN_SP_HTML)); return QString("%1").arg(str); } -bool BitcoinUnits::parse(int unit, const QString &value, CAmount *val_out) { +bool BitcoinUnits::parse(int unit, const QString &value, Amount *val_out) { if (!valid(unit) || value.isEmpty()) { // Refuse to parse invalid unit or empty string return false; } int num_decimals = decimals(unit); // Ignore spaces and thin spaces when parsing QStringList parts = removeSpaces(value).split("."); if (parts.size() > 2) { // More than one dot return false; } QString whole = parts[0]; QString decimals; if (parts.size() > 1) { decimals = parts[1]; } if (decimals.size() > num_decimals) { // Exceeds max precision return false; } bool ok = false; QString str = whole + decimals.leftJustified(num_decimals, '0'); if (str.size() > 18) { // Longer numbers will exceed 63 bits return false; } - CAmount retvalue(str.toLongLong(&ok)); + Amount retvalue(str.toLongLong(&ok)); if (val_out) { *val_out = retvalue; } return ok; } QString BitcoinUnits::getAmountColumnTitle(int unit) { QString amountTitle = QObject::tr("Amount"); if (BitcoinUnits::valid(unit)) { amountTitle += " (" + BitcoinUnits::name(unit) + ")"; } return amountTitle; } int BitcoinUnits::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return unitlist.size(); } QVariant BitcoinUnits::data(const QModelIndex &index, int role) const { int row = index.row(); if (row >= 0 && row < unitlist.size()) { Unit unit = unitlist.at(row); switch (role) { case Qt::EditRole: case Qt::DisplayRole: return QVariant(name(unit)); case Qt::ToolTipRole: return QVariant(description(unit)); case UnitRole: return QVariant(static_cast(unit)); } } return QVariant(); } -CAmount BitcoinUnits::maxMoney() { - return MAX_MONEY.GetSatoshis(); +Amount BitcoinUnits::maxMoney() { + return MAX_MONEY; } diff --git a/src/qt/bitcoinunits.h b/src/qt/bitcoinunits.h index c919a4364..3255b1422 100644 --- a/src/qt/bitcoinunits.h +++ b/src/qt/bitcoinunits.h @@ -1,127 +1,126 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_QT_BITCOINUNITS_H #define BITCOIN_QT_BITCOINUNITS_H #include "amount.h" #include #include // 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 " " // 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 " " // 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 " " // 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 " " // 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 \ " " // 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 and serves as * list model for drop-down selection boxes. */ class BitcoinUnits : public QAbstractListModel { Q_OBJECT public: explicit BitcoinUnits(QObject *parent); /** * Bitcoin units (Bitcoin Cash unit work the same as Bitoin). * @note Source: https://en.bitcoin.it/wiki/Units. * Please add only sensible ones. */ enum Unit { BCH, mBCH, uBCH }; enum SeparatorStyle { separatorNever, separatorStandard, separatorAlways }; //! @name Static API //! Unit conversion and formatting ///@{ //! Get list of units, for drop-down box static QList availableUnits(); //! Is unit ID valid? static bool valid(int unit); //! Short name static QString name(int unit); //! Longer description static QString description(int unit); //! Number of Satoshis (1e-8) per unit static qint64 factor(int unit); //! Number of decimals left static int decimals(int unit); //! Format as string - static QString format(int unit, const CAmount &amount, - bool plussign = false, + static QString format(int unit, const Amount amount, bool plussign = false, SeparatorStyle separators = separatorStandard); //! Format as string (with unit) static QString - formatWithUnit(int unit, const CAmount &amount, bool plussign = false, + formatWithUnit(int unit, const Amount amount, bool plussign = false, SeparatorStyle separators = separatorStandard); //! Format as HTML string (with unit) static QString - formatHtmlWithUnit(int unit, const CAmount &amount, bool plussign = false, + formatHtmlWithUnit(int unit, const Amount amount, bool plussign = false, SeparatorStyle separators = separatorStandard); //! Parse string to coin amount - static bool parse(int unit, const QString &value, CAmount *val_out); + static bool parse(int unit, const QString &value, Amount *val_out); //! Gets title for amount column including current display unit if //! optionsModel reference available */ static QString getAmountColumnTitle(int unit); ///@} //! @name AbstractListModel implementation //! List model for unit drop-down selection box. ///@{ enum RoleIndex { /** Unit identifier */ UnitRole = Qt::UserRole }; int rowCount(const QModelIndex &parent) 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; } //! Return maximum number of base units (Satoshis) - static CAmount maxMoney(); + static Amount maxMoney(); private: QList unitlist; }; typedef BitcoinUnits::Unit BitcoinUnit; #endif // BITCOIN_QT_BITCOINUNITS_H diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 7e2e4df2c..6c141e884 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -1,998 +1,999 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "guiutil.h" #include "bitcoinaddressvalidator.h" #include "bitcoinunits.h" #include "dstencode.h" #include "qvalidatedlineedit.h" #include "walletmodel.h" #include "config.h" #include "dstencode.h" #include "init.h" #include "policy/policy.h" #include "primitives/transaction.h" #include "protocol.h" #include "script/script.h" #include "script/standard.h" #include "util.h" #include "utilstrencodings.h" #ifdef WIN32 #ifdef _WIN32_WINNT #undef _WIN32_WINNT #endif #define _WIN32_WINNT 0x0501 #ifdef _WIN32_IE #undef _WIN32_IE #endif #define _WIN32_IE 0x0501 #define WIN32_LEAN_AND_MEAN 1 #ifndef NOMINMAX #define NOMINMAX #endif #include "shellapi.h" #include "shlobj.h" #include "shlwapi.h" #endif #include #include #if BOOST_FILESYSTEM_VERSION >= 3 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for Qt::mightBeRichText #include #if QT_VERSION < 0x050000 #include #else #include #endif #if QT_VERSION >= 0x50200 #include #endif #if BOOST_FILESYSTEM_VERSION >= 3 static boost::filesystem::detail::utf8_codecvt_facet utf8; #endif #if defined(Q_OS_MAC) // These Mac includes must be done in the global namespace #include #include extern double NSAppKitVersionNumber; #if !defined(NSAppKitVersionNumber10_8) #define NSAppKitVersionNumber10_8 1187 #endif #if !defined(NSAppKitVersionNumber10_9) #define NSAppKitVersionNumber10_9 1265 #endif #endif namespace GUIUtil { const QString URI_SCHEME("bitcoincash"); QString dateTimeStr(const QDateTime &date) { return date.date().toString(Qt::SystemLocaleShortDate) + QString(" ") + date.toString("hh:mm"); } QString dateTimeStr(qint64 nTime) { return dateTimeStr(QDateTime::fromTime_t((qint32)nTime)); } QFont fixedPitchFont() { #if QT_VERSION >= 0x50200 return QFontDatabase::systemFont(QFontDatabase::FixedFont); #else QFont font("Monospace"); #if QT_VERSION >= 0x040800 font.setStyleHint(QFont::Monospace); #else font.setStyleHint(QFont::TypeWriter); #endif return font; #endif } static std::string MakeAddrInvalid(std::string addr) { if (addr.size() < 2) { return ""; } // Checksum is at the end of the address. Swapping chars to make it invalid. std::swap(addr[addr.size() - 1], addr[addr.size() - 2]); if (!IsValidDestinationString(addr)) { return addr; } return ""; } std::string DummyAddress(const CChainParams ¶ms, const Config &cfg) { // Just some dummy data to generate an convincing random-looking (but // consistent) address static const std::vector dummydata = { 0xeb, 0x15, 0x23, 0x1d, 0xfc, 0xeb, 0x60, 0x92, 0x58, 0x86, 0xb6, 0x7d, 0x06, 0x52, 0x99, 0x92, 0x59, 0x15, 0xae, 0xb1}; const CTxDestination dstKey = CKeyID(uint160(dummydata)); return MakeAddrInvalid(EncodeDestination(dstKey, params, cfg)); } void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent) { parent->setFocusProxy(widget); widget->setFont(fixedPitchFont()); const CChainParams ¶ms = Params(); #if QT_VERSION >= 0x040700 // We don't want translators to use own addresses in translations // and this is the only place, where this address is supplied. widget->setPlaceholderText( QObject::tr("Enter a Bitcoin address (e.g. %1)") .arg(QString::fromStdString(DummyAddress(params, GetConfig())))); #endif widget->setValidator( new BitcoinAddressEntryValidator(params.CashAddrPrefix(), parent)); widget->setCheckValidator(new BitcoinAddressCheckValidator(parent)); } void setupAmountWidget(QLineEdit *widget, QWidget *parent) { QDoubleValidator *amountValidator = new QDoubleValidator(parent); amountValidator->setDecimals(8); amountValidator->setBottom(0.0); widget->setValidator(amountValidator); widget->setAlignment(Qt::AlignRight | Qt::AlignVCenter); } bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out) { // return if URI is not valid or is no bitcoincash: URI if (!uri.isValid() || uri.scheme() != URI_SCHEME) return false; SendCoinsRecipient rv; rv.address = uri.path(); // Trim any following forward slash which may have been added by the OS if (rv.address.endsWith("/")) { rv.address.truncate(rv.address.length() - 1); } rv.amount = 0; #if QT_VERSION < 0x050000 QList> items = uri.queryItems(); #else QUrlQuery uriQuery(uri); QList> items = uriQuery.queryItems(); #endif for (QList>::iterator i = items.begin(); i != items.end(); i++) { bool fShouldReturnFalse = false; if (i->first.startsWith("req-")) { i->first.remove(0, 4); fShouldReturnFalse = true; } if (i->first == "label") { rv.label = i->second; fShouldReturnFalse = false; } if (i->first == "message") { rv.message = i->second; fShouldReturnFalse = false; } else if (i->first == "amount") { if (!i->second.isEmpty()) { - if (!BitcoinUnits::parse(BitcoinUnits::BCH, i->second, - &rv.amount)) { + Amount amt; + if (!BitcoinUnits::parse(BitcoinUnits::BCH, i->second, &amt)) { return false; } + rv.amount = amt.GetSatoshis(); } fShouldReturnFalse = false; } if (fShouldReturnFalse) { return false; } } if (out) { *out = rv; } return true; } bool parseBitcoinURI(QString uri, SendCoinsRecipient *out) { // Convert bitcoincash:// to bitcoincash: // // Cannot handle this later, because bitcoincash:// // will cause Qt to see the part after // as host, // which will lower-case it (and thus invalidate the address). if (uri.startsWith(URI_SCHEME + "://", Qt::CaseInsensitive)) { uri.replace(0, URI_SCHEME.length() + 3, URI_SCHEME + ":"); } QUrl uriInstance(uri); return parseBitcoinURI(uriInstance, out); } QString formatBitcoinURI(const SendCoinsRecipient &info) { QString ret = (URI_SCHEME + ":%1").arg(info.address); int paramCount = 0; if (info.amount) { ret += QString("?amount=%1") .arg(BitcoinUnits::format(BitcoinUnits::BCH, info.amount, false, BitcoinUnits::separatorNever)); paramCount++; } if (!info.label.isEmpty()) { QString lbl(QUrl::toPercentEncoding(info.label)); ret += QString("%1label=%2").arg(paramCount == 0 ? "?" : "&").arg(lbl); paramCount++; } if (!info.message.isEmpty()) { QString msg(QUrl::toPercentEncoding(info.message)); ret += QString("%1message=%2").arg(paramCount == 0 ? "?" : "&").arg(msg); paramCount++; } return ret; } -bool isDust(const QString &address, const CAmount &amount) { +bool isDust(const QString &address, const Amount amount) { CTxDestination dest = DecodeDestination(address.toStdString()); CScript script = GetScriptForDestination(dest); - CTxOut txOut(Amount(amount), script); + CTxOut txOut(amount, script); return txOut.IsDust(dustRelayFee); } QString HtmlEscape(const QString &str, bool fMultiLine) { #if QT_VERSION < 0x050000 QString escaped = Qt::escape(str); #else QString escaped = str.toHtmlEscaped(); #endif if (fMultiLine) { escaped = escaped.replace("\n", "
\n"); } return escaped; } QString HtmlEscape(const std::string &str, bool fMultiLine) { return HtmlEscape(QString::fromStdString(str), fMultiLine); } void copyEntryData(QAbstractItemView *view, int column, int role) { if (!view || !view->selectionModel()) return; QModelIndexList selection = view->selectionModel()->selectedRows(column); if (!selection.isEmpty()) { // Copy first item setClipboard(selection.at(0).data(role).toString()); } } QList getEntryData(QAbstractItemView *view, int column) { if (!view || !view->selectionModel()) return QList(); return view->selectionModel()->selectedRows(column); } QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedSuffixOut) { QString selectedFilter; QString myDir; // Default to user documents location if (dir.isEmpty()) { #if QT_VERSION < 0x050000 myDir = QDesktopServices::storageLocation( QDesktopServices::DocumentsLocation); #else myDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); #endif } else { myDir = dir; } /* Directly convert path to native OS path separators */ QString result = QDir::toNativeSeparators(QFileDialog::getSaveFileName( parent, caption, myDir, filter, &selectedFilter)); /* Extract first suffix from filter pattern "Description (*.foo)" or * "Description (*.foo *.bar ...) */ QRegExp filter_re(".* \\(\\*\\.(.*)[ \\)]"); QString selectedSuffix; if (filter_re.exactMatch(selectedFilter)) { selectedSuffix = filter_re.cap(1); } /* Add suffix if needed */ QFileInfo info(result); if (!result.isEmpty()) { if (info.suffix().isEmpty() && !selectedSuffix.isEmpty()) { /* No suffix specified, add selected suffix */ if (!result.endsWith(".")) result.append("."); result.append(selectedSuffix); } } /* Return selected suffix if asked to */ if (selectedSuffixOut) { *selectedSuffixOut = selectedSuffix; } return result; } QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedSuffixOut) { QString selectedFilter; QString myDir; if (dir.isEmpty()) // Default to user documents location { #if QT_VERSION < 0x050000 myDir = QDesktopServices::storageLocation( QDesktopServices::DocumentsLocation); #else myDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); #endif } else { myDir = dir; } /* Directly convert path to native OS path separators */ QString result = QDir::toNativeSeparators(QFileDialog::getOpenFileName( parent, caption, myDir, filter, &selectedFilter)); if (selectedSuffixOut) { /* Extract first suffix from filter pattern "Description (*.foo)" or * "Description (*.foo *.bar ...) */ QRegExp filter_re(".* \\(\\*\\.(.*)[ \\)]"); QString selectedSuffix; if (filter_re.exactMatch(selectedFilter)) { selectedSuffix = filter_re.cap(1); } *selectedSuffixOut = selectedSuffix; } return result; } Qt::ConnectionType blockingGUIThreadConnection() { if (QThread::currentThread() != qApp->thread()) { return Qt::BlockingQueuedConnection; } else { return Qt::DirectConnection; } } bool checkPoint(const QPoint &p, const QWidget *w) { QWidget *atW = QApplication::widgetAt(w->mapToGlobal(p)); if (!atW) return false; return atW->topLevelWidget() == w; } bool isObscured(QWidget *w) { return !(checkPoint(QPoint(0, 0), w) && checkPoint(QPoint(w->width() - 1, 0), w) && checkPoint(QPoint(0, w->height() - 1), w) && checkPoint(QPoint(w->width() - 1, w->height() - 1), w) && checkPoint(QPoint(w->width() / 2, w->height() / 2), w)); } void openDebugLogfile() { boost::filesystem::path pathDebug = GetDataDir() / "debug.log"; /* Open debug.log with the associated application */ if (boost::filesystem::exists(pathDebug)) QDesktopServices::openUrl( QUrl::fromLocalFile(boostPathToQString(pathDebug))); } void SubstituteFonts(const QString &language) { #if defined(Q_OS_MAC) // Background: // OSX's default font changed in 10.9 and Qt is unable to find it with its // usual fallback methods when building against the 10.7 sdk or lower. // The 10.8 SDK added a function to let it find the correct fallback font. // If this fallback is not properly loaded, some characters may fail to // render correctly. // // The same thing happened with 10.10. .Helvetica Neue DeskInterface is now // default. // // Solution: If building with the 10.7 SDK or lower and the user's platform // is 10.9 or higher at runtime, substitute the correct font. This needs to // happen before the QApplication is created. #if defined(MAC_OS_X_VERSION_MAX_ALLOWED) && \ MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8 if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_8) { if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_9) /* On a 10.9 - 10.9.x system */ QFont::insertSubstitution(".Lucida Grande UI", "Lucida Grande"); else { /* 10.10 or later system */ if (language == "zh_CN" || language == "zh_TW" || language == "zh_HK") // traditional or simplified Chinese QFont::insertSubstitution(".Helvetica Neue DeskInterface", "Heiti SC"); else if (language == "ja") // Japanesee QFont::insertSubstitution(".Helvetica Neue DeskInterface", "Songti SC"); else QFont::insertSubstitution(".Helvetica Neue DeskInterface", "Lucida Grande"); } } #endif #endif } ToolTipToRichTextFilter::ToolTipToRichTextFilter(int _size_threshold, QObject *parent) : QObject(parent), size_threshold(_size_threshold) {} bool ToolTipToRichTextFilter::eventFilter(QObject *obj, QEvent *evt) { if (evt->type() == QEvent::ToolTipChange) { QWidget *widget = static_cast(obj); QString tooltip = widget->toolTip(); if (tooltip.size() > size_threshold && !tooltip.startsWith(" to make sure Qt detects this as rich text // Escape the current message as HTML and replace \n by
tooltip = "" + HtmlEscape(tooltip, true) + ""; widget->setToolTip(tooltip); return true; } } return QObject::eventFilter(obj, evt); } void TableViewLastColumnResizingFixer::connectViewHeadersSignals() { connect(tableView->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), this, SLOT(on_sectionResized(int, int, int))); connect(tableView->horizontalHeader(), SIGNAL(geometriesChanged()), this, SLOT(on_geometriesChanged())); } // We need to disconnect these while handling the resize events, otherwise we // can enter infinite loops. void TableViewLastColumnResizingFixer::disconnectViewHeadersSignals() { disconnect(tableView->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), this, SLOT(on_sectionResized(int, int, int))); disconnect(tableView->horizontalHeader(), SIGNAL(geometriesChanged()), this, SLOT(on_geometriesChanged())); } // Setup the resize mode, handles compatibility for Qt5 and below as the method // signatures changed. // Refactored here for readability. void TableViewLastColumnResizingFixer::setViewHeaderResizeMode( int logicalIndex, QHeaderView::ResizeMode resizeMode) { #if QT_VERSION < 0x050000 tableView->horizontalHeader()->setResizeMode(logicalIndex, resizeMode); #else tableView->horizontalHeader()->setSectionResizeMode(logicalIndex, resizeMode); #endif } void TableViewLastColumnResizingFixer::resizeColumn(int nColumnIndex, int width) { tableView->setColumnWidth(nColumnIndex, width); tableView->horizontalHeader()->resizeSection(nColumnIndex, width); } int TableViewLastColumnResizingFixer::getColumnsWidth() { int nColumnsWidthSum = 0; for (int i = 0; i < columnCount; i++) { nColumnsWidthSum += tableView->horizontalHeader()->sectionSize(i); } return nColumnsWidthSum; } int TableViewLastColumnResizingFixer::getAvailableWidthForColumn(int column) { int nResult = lastColumnMinimumWidth; int nTableWidth = tableView->horizontalHeader()->width(); if (nTableWidth > 0) { int nOtherColsWidth = getColumnsWidth() - tableView->horizontalHeader()->sectionSize(column); nResult = std::max(nResult, nTableWidth - nOtherColsWidth); } return nResult; } // Make sure we don't make the columns wider than the table's viewport width. void TableViewLastColumnResizingFixer::adjustTableColumnsWidth() { disconnectViewHeadersSignals(); resizeColumn(lastColumnIndex, getAvailableWidthForColumn(lastColumnIndex)); connectViewHeadersSignals(); int nTableWidth = tableView->horizontalHeader()->width(); int nColsWidth = getColumnsWidth(); if (nColsWidth > nTableWidth) { resizeColumn(secondToLastColumnIndex, getAvailableWidthForColumn(secondToLastColumnIndex)); } } // Make column use all the space available, useful during window resizing. void TableViewLastColumnResizingFixer::stretchColumnWidth(int column) { disconnectViewHeadersSignals(); resizeColumn(column, getAvailableWidthForColumn(column)); connectViewHeadersSignals(); } // When a section is resized this is a slot-proxy for ajustAmountColumnWidth(). void TableViewLastColumnResizingFixer::on_sectionResized(int logicalIndex, int oldSize, int newSize) { adjustTableColumnsWidth(); int remainingWidth = getAvailableWidthForColumn(logicalIndex); if (newSize > remainingWidth) { resizeColumn(logicalIndex, remainingWidth); } } // When the table's geometry is ready, we manually perform the stretch of the // "Message" column, // as the "Stretch" resize mode does not allow for interactive resizing. void TableViewLastColumnResizingFixer::on_geometriesChanged() { if ((getColumnsWidth() - this->tableView->horizontalHeader()->width()) != 0) { disconnectViewHeadersSignals(); resizeColumn(secondToLastColumnIndex, getAvailableWidthForColumn(secondToLastColumnIndex)); connectViewHeadersSignals(); } } /** * Initializes all internal variables and prepares the * the resize modes of the last 2 columns of the table and */ TableViewLastColumnResizingFixer::TableViewLastColumnResizingFixer( QTableView *table, int lastColMinimumWidth, int allColsMinimumWidth, QObject *parent) : QObject(parent), tableView(table), lastColumnMinimumWidth(lastColMinimumWidth), allColumnsMinimumWidth(allColsMinimumWidth) { columnCount = tableView->horizontalHeader()->count(); lastColumnIndex = columnCount - 1; secondToLastColumnIndex = columnCount - 2; tableView->horizontalHeader()->setMinimumSectionSize( allColumnsMinimumWidth); setViewHeaderResizeMode(secondToLastColumnIndex, QHeaderView::Interactive); setViewHeaderResizeMode(lastColumnIndex, QHeaderView::Interactive); } #ifdef WIN32 static boost::filesystem::path StartupShortcutPath() { std::string chain = ChainNameFromCommandLine(); if (chain == CBaseChainParams::MAIN) return GetSpecialFolderPath(CSIDL_STARTUP) / "Bitcoin.lnk"; // Remove this special case when CBaseChainParams::TESTNET = "testnet4" if (chain == CBaseChainParams::TESTNET) return GetSpecialFolderPath(CSIDL_STARTUP) / "Bitcoin (testnet).lnk"; return GetSpecialFolderPath(CSIDL_STARTUP) / strprintf("Bitcoin (%s).lnk", chain); } bool GetStartOnSystemStartup() { // check for Bitcoin*.lnk return boost::filesystem::exists(StartupShortcutPath()); } bool SetStartOnSystemStartup(bool fAutoStart) { // If the shortcut exists already, remove it for updating boost::filesystem::remove(StartupShortcutPath()); if (fAutoStart) { CoInitialize(nullptr); // Get a pointer to the IShellLink interface. IShellLink *psl = nullptr; HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_cast(&psl)); if (SUCCEEDED(hres)) { // Get the current executable path TCHAR pszExePath[MAX_PATH]; GetModuleFileName(nullptr, pszExePath, sizeof(pszExePath)); // Start client minimized QString strArgs = "-min"; // Set -testnet /-regtest options strArgs += QString::fromStdString(strprintf( " -testnet=%d -regtest=%d", GetBoolArg("-testnet", false), GetBoolArg("-regtest", false))); #ifdef UNICODE boost::scoped_array args(new TCHAR[strArgs.length() + 1]); // Convert the QString to TCHAR* strArgs.toWCharArray(args.get()); // Add missing '\0'-termination to string args[strArgs.length()] = '\0'; #endif // Set the path to the shortcut target psl->SetPath(pszExePath); PathRemoveFileSpec(pszExePath); psl->SetWorkingDirectory(pszExePath); psl->SetShowCmd(SW_SHOWMINNOACTIVE); #ifndef UNICODE psl->SetArguments(strArgs.toStdString().c_str()); #else psl->SetArguments(args.get()); #endif // Query IShellLink for the IPersistFile interface for // saving the shortcut in persistent storage. IPersistFile *ppf = nullptr; hres = psl->QueryInterface(IID_IPersistFile, reinterpret_cast(&ppf)); if (SUCCEEDED(hres)) { WCHAR pwsz[MAX_PATH]; // Ensure that the string is ANSI. MultiByteToWideChar(CP_ACP, 0, StartupShortcutPath().string().c_str(), -1, pwsz, MAX_PATH); // Save the link by calling IPersistFile::Save. hres = ppf->Save(pwsz, TRUE); ppf->Release(); psl->Release(); CoUninitialize(); return true; } psl->Release(); } CoUninitialize(); return false; } return true; } #elif defined(Q_OS_LINUX) // Follow the Desktop Application Autostart Spec: // http://standards.freedesktop.org/autostart-spec/autostart-spec-latest.html static boost::filesystem::path GetAutostartDir() { namespace fs = boost::filesystem; char *pszConfigHome = getenv("XDG_CONFIG_HOME"); if (pszConfigHome) return fs::path(pszConfigHome) / "autostart"; char *pszHome = getenv("HOME"); if (pszHome) return fs::path(pszHome) / ".config" / "autostart"; return fs::path(); } static boost::filesystem::path GetAutostartFilePath() { std::string chain = ChainNameFromCommandLine(); if (chain == CBaseChainParams::MAIN) return GetAutostartDir() / "bitcoin.desktop"; return GetAutostartDir() / strprintf("bitcoin-%s.lnk", chain); } bool GetStartOnSystemStartup() { boost::filesystem::ifstream optionFile(GetAutostartFilePath()); if (!optionFile.good()) return false; // Scan through file for "Hidden=true": std::string line; while (!optionFile.eof()) { getline(optionFile, line); if (line.find("Hidden") != std::string::npos && line.find("true") != std::string::npos) return false; } optionFile.close(); return true; } bool SetStartOnSystemStartup(bool fAutoStart) { if (!fAutoStart) boost::filesystem::remove(GetAutostartFilePath()); else { char pszExePath[MAX_PATH + 1]; memset(pszExePath, 0, sizeof(pszExePath)); if (readlink("/proc/self/exe", pszExePath, sizeof(pszExePath) - 1) == -1) return false; boost::filesystem::create_directories(GetAutostartDir()); boost::filesystem::ofstream optionFile( GetAutostartFilePath(), std::ios_base::out | std::ios_base::trunc); if (!optionFile.good()) return false; std::string chain = ChainNameFromCommandLine(); // Write a bitcoin.desktop file to the autostart directory: optionFile << "[Desktop Entry]\n"; optionFile << "Type=Application\n"; if (chain == CBaseChainParams::MAIN) optionFile << "Name=Bitcoin\n"; else optionFile << strprintf("Name=Bitcoin (%s)\n", chain); optionFile << "Exec=" << pszExePath << strprintf(" -min -testnet=%d -regtest=%d\n", GetBoolArg("-testnet", false), GetBoolArg("-regtest", false)); optionFile << "Terminal=false\n"; optionFile << "Hidden=false\n"; optionFile.close(); } return true; } #elif defined(Q_OS_MAC) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" // based on: // https://github.com/Mozketo/LaunchAtLoginController/blob/master/LaunchAtLoginController.m // NB: caller must release returned ref if it's not NULL LSSharedFileListItemRef findStartupItemInList(LSSharedFileListRef list, CFURLRef findUrl); LSSharedFileListItemRef findStartupItemInList(LSSharedFileListRef list, CFURLRef findUrl) { LSSharedFileListItemRef foundItem = nullptr; // loop through the list of startup items and try to find the bitcoin app CFArrayRef listSnapshot = LSSharedFileListCopySnapshot(list, nullptr); for (int i = 0; !foundItem && i < CFArrayGetCount(listSnapshot); ++i) { LSSharedFileListItemRef item = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(listSnapshot, i); UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes; CFURLRef currentItemURL = nullptr; #if defined(MAC_OS_X_VERSION_MAX_ALLOWED) && \ MAC_OS_X_VERSION_MAX_ALLOWED >= 10100 if (&LSSharedFileListItemCopyResolvedURL) currentItemURL = LSSharedFileListItemCopyResolvedURL( item, resolutionFlags, nullptr); #if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && \ MAC_OS_X_VERSION_MIN_REQUIRED < 10100 else LSSharedFileListItemResolve(item, resolutionFlags, ¤tItemURL, nullptr); #endif #else LSSharedFileListItemResolve(item, resolutionFlags, ¤tItemURL, nullptr); #endif if (currentItemURL && CFEqual(currentItemURL, findUrl)) { // found CFRetain(foundItem = item); } if (currentItemURL) { CFRelease(currentItemURL); } } CFRelease(listSnapshot); return foundItem; } bool GetStartOnSystemStartup() { CFURLRef bitcoinAppUrl = CFBundleCopyBundleURL(CFBundleGetMainBundle()); LSSharedFileListRef loginItems = LSSharedFileListCreate( nullptr, kLSSharedFileListSessionLoginItems, nullptr); LSSharedFileListItemRef foundItem = findStartupItemInList(loginItems, bitcoinAppUrl); // findStartupItemInList retains the item it returned, need to release if (foundItem) { CFRelease(foundItem); } CFRelease(loginItems); CFRelease(bitcoinAppUrl); return foundItem; } bool SetStartOnSystemStartup(bool fAutoStart) { CFURLRef bitcoinAppUrl = CFBundleCopyBundleURL(CFBundleGetMainBundle()); LSSharedFileListRef loginItems = LSSharedFileListCreate( nullptr, kLSSharedFileListSessionLoginItems, nullptr); LSSharedFileListItemRef foundItem = findStartupItemInList(loginItems, bitcoinAppUrl); if (fAutoStart && !foundItem) { // add bitcoin app to startup item list LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst, nullptr, nullptr, bitcoinAppUrl, nullptr, nullptr); } else if (!fAutoStart && foundItem) { // remove item LSSharedFileListItemRemove(loginItems, foundItem); } // findStartupItemInList retains the item it returned, need to release if (foundItem) { CFRelease(foundItem); } CFRelease(loginItems); CFRelease(bitcoinAppUrl); return true; } #pragma GCC diagnostic pop #else bool GetStartOnSystemStartup() { return false; } bool SetStartOnSystemStartup(bool fAutoStart) { return false; } #endif void saveWindowGeometry(const QString &strSetting, QWidget *parent) { QSettings settings; settings.setValue(strSetting + "Pos", parent->pos()); settings.setValue(strSetting + "Size", parent->size()); } void restoreWindowGeometry(const QString &strSetting, const QSize &defaultSize, QWidget *parent) { QSettings settings; QPoint pos = settings.value(strSetting + "Pos").toPoint(); QSize size = settings.value(strSetting + "Size", defaultSize).toSize(); if (!pos.x() && !pos.y()) { QRect screen = QApplication::desktop()->screenGeometry(); pos.setX((screen.width() - size.width()) / 2); pos.setY((screen.height() - size.height()) / 2); } parent->resize(size); parent->move(pos); } void setClipboard(const QString &str) { QApplication::clipboard()->setText(str, QClipboard::Clipboard); QApplication::clipboard()->setText(str, QClipboard::Selection); } #if BOOST_FILESYSTEM_VERSION >= 3 boost::filesystem::path qstringToBoostPath(const QString &path) { return boost::filesystem::path(path.toStdString(), utf8); } QString boostPathToQString(const boost::filesystem::path &path) { return QString::fromStdString(path.string(utf8)); } #else #warning Conversion between boost path and QString can use invalid character encoding with boost_filesystem v2 and older boost::filesystem::path qstringToBoostPath(const QString &path) { return boost::filesystem::path(path.toStdString()); } QString boostPathToQString(const boost::filesystem::path &path) { return QString::fromStdString(path.string()); } #endif QString formatDurationStr(int secs) { QStringList strList; int days = secs / 86400; int hours = (secs % 86400) / 3600; int mins = (secs % 3600) / 60; int seconds = secs % 60; if (days) strList.append(QString(QObject::tr("%1 d")).arg(days)); if (hours) strList.append(QString(QObject::tr("%1 h")).arg(hours)); if (mins) strList.append(QString(QObject::tr("%1 m")).arg(mins)); if (seconds || (!days && !hours && !mins)) strList.append(QString(QObject::tr("%1 s")).arg(seconds)); return strList.join(" "); } QString formatServicesStr(quint64 mask) { QStringList strList; // Just scan the last 8 bits for now. for (int i = 0; i < 8; i++) { uint64_t check = 1 << i; if (mask & check) { switch (check) { case NODE_NETWORK: strList.append("NETWORK"); break; case NODE_GETUTXO: strList.append("GETUTXO"); break; case NODE_BLOOM: strList.append("BLOOM"); break; case NODE_XTHIN: strList.append("XTHIN"); break; case NODE_BITCOIN_CASH: strList.append("CASH"); break; default: strList.append(QString("%1[%2]").arg("UNKNOWN").arg(check)); } } } if (strList.size()) return strList.join(" & "); else return QObject::tr("None"); } QString formatPingTime(double dPingTime) { return (dPingTime == std::numeric_limits::max() / 1e6 || dPingTime == 0) ? QObject::tr("N/A") : QString(QObject::tr("%1 ms")) .arg(QString::number((int)(dPingTime * 1000), 10)); } QString formatTimeOffset(int64_t nTimeOffset) { return QString(QObject::tr("%1 s")) .arg(QString::number((int)nTimeOffset, 10)); } QString formatNiceTimeOffset(qint64 secs) { // Represent time from last generated block in human readable text QString timeBehindText; const int HOUR_IN_SECONDS = 60 * 60; const int DAY_IN_SECONDS = 24 * 60 * 60; const int WEEK_IN_SECONDS = 7 * 24 * 60 * 60; // Average length of year in Gregorian calendar const int YEAR_IN_SECONDS = 31556952; if (secs < 60) { timeBehindText = QObject::tr("%n second(s)", "", secs); } else if (secs < 2 * HOUR_IN_SECONDS) { timeBehindText = QObject::tr("%n minute(s)", "", secs / 60); } else if (secs < 2 * DAY_IN_SECONDS) { timeBehindText = QObject::tr("%n hour(s)", "", secs / HOUR_IN_SECONDS); } else if (secs < 2 * WEEK_IN_SECONDS) { timeBehindText = QObject::tr("%n day(s)", "", secs / DAY_IN_SECONDS); } else if (secs < YEAR_IN_SECONDS) { timeBehindText = QObject::tr("%n week(s)", "", secs / WEEK_IN_SECONDS); } else { qint64 years = secs / YEAR_IN_SECONDS; qint64 remainder = secs % YEAR_IN_SECONDS; timeBehindText = QObject::tr("%1 and %2") .arg(QObject::tr("%n year(s)", "", years)) .arg(QObject::tr("%n week(s)", "", remainder / WEEK_IN_SECONDS)); } return timeBehindText; } void ClickableLabel::mouseReleaseEvent(QMouseEvent *event) { Q_EMIT clicked(event->pos()); } void ClickableProgressBar::mouseReleaseEvent(QMouseEvent *event) { Q_EMIT clicked(event->pos()); } } // namespace GUIUtil diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index e1a2b5f7c..82a44ea98 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -1,274 +1,274 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_QT_GUIUTIL_H #define BITCOIN_QT_GUIUTIL_H #include "amount.h" #include #include #include #include #include #include #include #include #include class QValidatedLineEdit; class SendCoinsRecipient; class CChainParams; class Config; QT_BEGIN_NAMESPACE class QAbstractItemView; class QDateTime; class QFont; class QLineEdit; class QUrl; class QWidget; QT_END_NAMESPACE /** Utility functions used by the Bitcoin Qt UI. */ namespace GUIUtil { extern const QString URI_SCHEME; // Create human-readable string from date QString dateTimeStr(const QDateTime &datetime); QString dateTimeStr(qint64 nTime); // Return a monospace font QFont fixedPitchFont(); // Generate an invalid, but convincing address. std::string DummyAddress(const CChainParams ¶ms, const Config &cfg); // Set up widgets for address and amounts void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent); void setupAmountWidget(QLineEdit *widget, QWidget *parent); // Parse "bitcoincash:" URI into recipient object, return true on successful // parsing bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out); bool parseBitcoinURI(QString uri, SendCoinsRecipient *out); QString formatBitcoinURI(const SendCoinsRecipient &info); // Returns true if given address+amount meets "dust" definition -bool isDust(const QString &address, const CAmount &amount); +bool isDust(const QString &address, const Amount amount); // HTML escaping for rich text controls QString HtmlEscape(const QString &str, bool fMultiLine = false); QString HtmlEscape(const std::string &str, bool fMultiLine = false); /** Copy a field of the currently selected entry of a view to the clipboard. Does nothing if nothing is selected. @param[in] column Data column to extract from the model @param[in] role Data role to extract from the model @see TransactionView::copyLabel, TransactionView::copyAmount, TransactionView::copyAddress */ void copyEntryData(QAbstractItemView *view, int column, int role = Qt::EditRole); /** Return a field of the currently selected entry as a QString. Does nothing if nothing is selected. @param[in] column Data column to extract from the model @see TransactionView::copyLabel, TransactionView::copyAmount, TransactionView::copyAddress */ QList getEntryData(QAbstractItemView *view, int column); void setClipboard(const QString &str); /** Get save filename, mimics QFileDialog::getSaveFileName, except that it appends a default suffix when no suffix is provided by the user. @param[in] parent Parent window (or 0) @param[in] caption Window caption (or empty, for default) @param[in] dir Starting directory (or empty, to default to documents directory) @param[in] filter Filter specification such as "Comma Separated Files (*.csv)" @param[out] selectedSuffixOut Pointer to return the suffix (file type) that was selected (or 0). Can be useful when choosing the save file format based on suffix. */ QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedSuffixOut); /** Get open filename, convenience wrapper for QFileDialog::getOpenFileName. @param[in] parent Parent window (or 0) @param[in] caption Window caption (or empty, for default) @param[in] dir Starting directory (or empty, to default to documents directory) @param[in] filter Filter specification such as "Comma Separated Files (*.csv)" @param[out] selectedSuffixOut Pointer to return the suffix (file type) that was selected (or 0). Can be useful when choosing the save file format based on suffix. */ QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedSuffixOut); /** Get connection type to call object slot in GUI thread with invokeMethod. The call will be blocking. @returns If called from the GUI thread, return a Qt::DirectConnection. If called from another thread, return a Qt::BlockingQueuedConnection. */ Qt::ConnectionType blockingGUIThreadConnection(); // Determine whether a widget is hidden behind other windows bool isObscured(QWidget *w); // Open debug.log void openDebugLogfile(); // Replace invalid default fonts with known good ones void SubstituteFonts(const QString &language); /** Qt event filter that intercepts ToolTipChange events, and replaces the * tooltip with a rich text representation if needed. This assures that Qt can * word-wrap long tooltip messages. Tooltips longer than the provided size * threshold (in characters) are wrapped. */ class ToolTipToRichTextFilter : public QObject { Q_OBJECT public: explicit ToolTipToRichTextFilter(int size_threshold, QObject *parent = 0); protected: bool eventFilter(QObject *obj, QEvent *evt); private: int size_threshold; }; /** * Makes a QTableView last column feel as if it was being resized from its left * border. * Also makes sure the column widths are never larger than the table's viewport. * In Qt, all columns are resizable from the right, but it's not intuitive * resizing the last column from the right. * Usually our second to last columns behave as if stretched, and when on strech * mode, columns aren't resizable interactively or programmatically. * * This helper object takes care of this issue. * */ class TableViewLastColumnResizingFixer : public QObject { Q_OBJECT public: TableViewLastColumnResizingFixer(QTableView *table, int lastColMinimumWidth, int allColsMinimumWidth, QObject *parent); void stretchColumnWidth(int column); private: QTableView *tableView; int lastColumnMinimumWidth; int allColumnsMinimumWidth; int lastColumnIndex; int columnCount; int secondToLastColumnIndex; void adjustTableColumnsWidth(); int getAvailableWidthForColumn(int column); int getColumnsWidth(); void connectViewHeadersSignals(); void disconnectViewHeadersSignals(); void setViewHeaderResizeMode(int logicalIndex, QHeaderView::ResizeMode resizeMode); void resizeColumn(int nColumnIndex, int width); private Q_SLOTS: void on_sectionResized(int logicalIndex, int oldSize, int newSize); void on_geometriesChanged(); }; bool GetStartOnSystemStartup(); bool SetStartOnSystemStartup(bool fAutoStart); /** Save window size and position */ void saveWindowGeometry(const QString &strSetting, QWidget *parent); /** Restore window size and position */ void restoreWindowGeometry(const QString &strSetting, const QSize &defaultSizeIn, QWidget *parent); /* Convert QString to OS specific boost path through UTF-8 */ boost::filesystem::path qstringToBoostPath(const QString &path); /* Convert OS specific boost path to QString through UTF-8 */ QString boostPathToQString(const boost::filesystem::path &path); /* Convert seconds into a QString with days, hours, mins, secs */ QString formatDurationStr(int secs); /* Format CNodeStats.nServices bitmask into a user-readable string */ QString formatServicesStr(quint64 mask); /* Format a CNodeCombinedStats.dPingTime into a user-readable string or display * N/A, if 0*/ QString formatPingTime(double dPingTime); /* Format a CNodeCombinedStats.nTimeOffset into a user-readable string. */ QString formatTimeOffset(int64_t nTimeOffset); QString formatNiceTimeOffset(qint64 secs); class ClickableLabel : public QLabel { Q_OBJECT Q_SIGNALS: /** Emitted when the label is clicked. The relative mouse coordinates of the * click are passed to the signal. */ void clicked(const QPoint &point); protected: void mouseReleaseEvent(QMouseEvent *event); }; class ClickableProgressBar : public QProgressBar { Q_OBJECT Q_SIGNALS: /** Emitted when the progressbar is clicked. The relative mouse coordinates * of the click are passed to the signal. */ void clicked(const QPoint &point); protected: void mouseReleaseEvent(QMouseEvent *event); }; #if defined(Q_OS_MAC) && QT_VERSION >= 0x050000 // workaround for Qt OSX Bug: // https://bugreports.qt-project.org/browse/QTBUG-15631 // QProgressBar uses around 10% CPU even when app is in background class ProgressBar : public ClickableProgressBar { bool event(QEvent *e) { return (e->type() != QEvent::StyleAnimationUpdate) ? QProgressBar::event(e) : false; } }; #else typedef ClickableProgressBar ProgressBar; #endif } // namespace GUIUtil #endif // BITCOIN_QT_GUIUTIL_H diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index 491708097..685bbdd11 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -1,592 +1,592 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "transactionview.h" #include "addresstablemodel.h" #include "bitcoinunits.h" #include "csvmodelwriter.h" #include "editaddressdialog.h" #include "guiutil.h" #include "optionsmodel.h" #include "platformstyle.h" #include "transactiondescdialog.h" #include "transactionfilterproxy.h" #include "transactionrecord.h" #include "transactiontablemodel.h" #include "walletmodel.h" #include "ui_interface.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *parent) : QWidget(parent), model(0), transactionProxyModel(0), transactionView(0), abandonAction(0), columnResizingFixer(0) { // Build filter row setContentsMargins(0, 0, 0, 0); QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setContentsMargins(0, 0, 0, 0); if (platformStyle->getUseExtraSpacing()) { hlayout->setSpacing(5); hlayout->addSpacing(26); } else { hlayout->setSpacing(0); hlayout->addSpacing(23); } watchOnlyWidget = new QComboBox(this); watchOnlyWidget->setFixedWidth(24); watchOnlyWidget->addItem("", TransactionFilterProxy::WatchOnlyFilter_All); watchOnlyWidget->addItem(platformStyle->SingleColorIcon(":/icons/eye_plus"), "", TransactionFilterProxy::WatchOnlyFilter_Yes); watchOnlyWidget->addItem( platformStyle->SingleColorIcon(":/icons/eye_minus"), "", TransactionFilterProxy::WatchOnlyFilter_No); hlayout->addWidget(watchOnlyWidget); dateWidget = new QComboBox(this); if (platformStyle->getUseExtraSpacing()) { dateWidget->setFixedWidth(121); } else { dateWidget->setFixedWidth(120); } dateWidget->addItem(tr("All"), All); dateWidget->addItem(tr("Today"), Today); dateWidget->addItem(tr("This week"), ThisWeek); dateWidget->addItem(tr("This month"), ThisMonth); dateWidget->addItem(tr("Last month"), LastMonth); dateWidget->addItem(tr("This year"), ThisYear); dateWidget->addItem(tr("Range..."), Range); hlayout->addWidget(dateWidget); typeWidget = new QComboBox(this); if (platformStyle->getUseExtraSpacing()) { typeWidget->setFixedWidth(121); } else { typeWidget->setFixedWidth(120); } typeWidget->addItem(tr("All"), TransactionFilterProxy::ALL_TYPES); typeWidget->addItem( tr("Received with"), TransactionFilterProxy::TYPE(TransactionRecord::RecvWithAddress) | TransactionFilterProxy::TYPE(TransactionRecord::RecvFromOther)); typeWidget->addItem( tr("Sent to"), TransactionFilterProxy::TYPE(TransactionRecord::SendToAddress) | TransactionFilterProxy::TYPE(TransactionRecord::SendToOther)); typeWidget->addItem(tr("To yourself"), TransactionFilterProxy::TYPE( TransactionRecord::SendToSelf)); typeWidget->addItem(tr("Mined"), TransactionFilterProxy::TYPE( TransactionRecord::Generated)); typeWidget->addItem(tr("Other"), TransactionFilterProxy::TYPE(TransactionRecord::Other)); hlayout->addWidget(typeWidget); addressWidget = new QLineEdit(this); #if QT_VERSION >= 0x040700 addressWidget->setPlaceholderText(tr("Enter address or label to search")); #endif hlayout->addWidget(addressWidget); amountWidget = new QLineEdit(this); #if QT_VERSION >= 0x040700 amountWidget->setPlaceholderText(tr("Min amount")); #endif if (platformStyle->getUseExtraSpacing()) { amountWidget->setFixedWidth(97); } else { amountWidget->setFixedWidth(100); } amountWidget->setValidator(new QDoubleValidator(0, 1e20, 8, this)); hlayout->addWidget(amountWidget); QVBoxLayout *vlayout = new QVBoxLayout(this); vlayout->setContentsMargins(0, 0, 0, 0); vlayout->setSpacing(0); QTableView *view = new QTableView(this); vlayout->addLayout(hlayout); vlayout->addWidget(createDateRangeWidget()); vlayout->addWidget(view); vlayout->setSpacing(0); int width = view->verticalScrollBar()->sizeHint().width(); // Cover scroll bar width with spacing if (platformStyle->getUseExtraSpacing()) { hlayout->addSpacing(width + 2); } else { hlayout->addSpacing(width); } // Always show scroll bar view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); view->setTabKeyNavigation(false); view->setContextMenuPolicy(Qt::CustomContextMenu); view->installEventFilter(this); transactionView = view; // Actions abandonAction = new QAction(tr("Abandon transaction"), this); QAction *copyAddressAction = new QAction(tr("Copy address"), this); QAction *copyLabelAction = new QAction(tr("Copy label"), this); QAction *copyAmountAction = new QAction(tr("Copy amount"), this); QAction *copyTxIDAction = new QAction(tr("Copy transaction ID"), this); QAction *copyTxHexAction = new QAction(tr("Copy raw transaction"), this); QAction *copyTxPlainText = new QAction(tr("Copy full transaction details"), this); QAction *editLabelAction = new QAction(tr("Edit label"), this); QAction *showDetailsAction = new QAction(tr("Show transaction details"), this); contextMenu = new QMenu(this); contextMenu->addAction(copyAddressAction); contextMenu->addAction(copyLabelAction); contextMenu->addAction(copyAmountAction); contextMenu->addAction(copyTxIDAction); contextMenu->addAction(copyTxHexAction); contextMenu->addAction(copyTxPlainText); contextMenu->addAction(showDetailsAction); contextMenu->addSeparator(); contextMenu->addAction(abandonAction); contextMenu->addAction(editLabelAction); mapperThirdPartyTxUrls = new QSignalMapper(this); // Connect actions connect(mapperThirdPartyTxUrls, SIGNAL(mapped(QString)), this, SLOT(openThirdPartyTxUrl(QString))); connect(dateWidget, SIGNAL(activated(int)), this, SLOT(chooseDate(int))); connect(typeWidget, SIGNAL(activated(int)), this, SLOT(chooseType(int))); connect(watchOnlyWidget, SIGNAL(activated(int)), this, SLOT(chooseWatchonly(int))); connect(addressWidget, SIGNAL(textChanged(QString)), this, SLOT(changedPrefix(QString))); connect(amountWidget, SIGNAL(textChanged(QString)), this, SLOT(changedAmount(QString))); connect(view, SIGNAL(doubleClicked(QModelIndex)), this, SIGNAL(doubleClicked(QModelIndex))); connect(view, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextualMenu(QPoint))); connect(abandonAction, SIGNAL(triggered()), this, SLOT(abandonTx())); connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(copyAddress())); connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel())); connect(copyAmountAction, SIGNAL(triggered()), this, SLOT(copyAmount())); connect(copyTxIDAction, SIGNAL(triggered()), this, SLOT(copyTxID())); connect(copyTxHexAction, SIGNAL(triggered()), this, SLOT(copyTxHex())); connect(copyTxPlainText, SIGNAL(triggered()), this, SLOT(copyTxPlainText())); connect(editLabelAction, SIGNAL(triggered()), this, SLOT(editLabel())); connect(showDetailsAction, SIGNAL(triggered()), this, SLOT(showDetails())); } void TransactionView::setModel(WalletModel *_model) { this->model = _model; if (_model) { transactionProxyModel = new TransactionFilterProxy(this); transactionProxyModel->setSourceModel( _model->getTransactionTableModel()); transactionProxyModel->setDynamicSortFilter(true); transactionProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); transactionProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); transactionProxyModel->setSortRole(Qt::EditRole); transactionView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); transactionView->setModel(transactionProxyModel); transactionView->setAlternatingRowColors(true); transactionView->setSelectionBehavior(QAbstractItemView::SelectRows); transactionView->setSelectionMode(QAbstractItemView::ExtendedSelection); transactionView->setSortingEnabled(true); transactionView->sortByColumn(TransactionTableModel::Date, Qt::DescendingOrder); transactionView->verticalHeader()->hide(); transactionView->setColumnWidth(TransactionTableModel::Status, STATUS_COLUMN_WIDTH); transactionView->setColumnWidth(TransactionTableModel::Watchonly, WATCHONLY_COLUMN_WIDTH); transactionView->setColumnWidth(TransactionTableModel::Date, DATE_COLUMN_WIDTH); transactionView->setColumnWidth(TransactionTableModel::Type, TYPE_COLUMN_WIDTH); transactionView->setColumnWidth(TransactionTableModel::Amount, AMOUNT_MINIMUM_COLUMN_WIDTH); columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer( transactionView, AMOUNT_MINIMUM_COLUMN_WIDTH, MINIMUM_COLUMN_WIDTH, this); if (_model->getOptionsModel()) { // Add third party transaction URLs to context menu QStringList listUrls = _model->getOptionsModel()->getThirdPartyTxUrls().split( "|", QString::SkipEmptyParts); for (int i = 0; i < listUrls.size(); ++i) { QString host = QUrl(listUrls[i].trimmed(), QUrl::StrictMode).host(); if (!host.isEmpty()) { QAction *thirdPartyTxUrlAction = new QAction(host, this); // use host as menu item label if (i == 0) contextMenu->addSeparator(); contextMenu->addAction(thirdPartyTxUrlAction); connect(thirdPartyTxUrlAction, SIGNAL(triggered()), mapperThirdPartyTxUrls, SLOT(map())); mapperThirdPartyTxUrls->setMapping(thirdPartyTxUrlAction, listUrls[i].trimmed()); } } } // show/hide column Watch-only updateWatchOnlyColumn(_model->haveWatchOnly()); // Watch-only signal connect(_model, SIGNAL(notifyWatchonlyChanged(bool)), this, SLOT(updateWatchOnlyColumn(bool))); } } void TransactionView::chooseDate(int idx) { if (!transactionProxyModel) return; QDate current = QDate::currentDate(); dateRangeWidget->setVisible(false); switch (dateWidget->itemData(idx).toInt()) { case All: transactionProxyModel->setDateRange( TransactionFilterProxy::MIN_DATE, TransactionFilterProxy::MAX_DATE); break; case Today: transactionProxyModel->setDateRange( QDateTime(current), TransactionFilterProxy::MAX_DATE); break; case ThisWeek: { // Find last Monday QDate startOfWeek = current.addDays(-(current.dayOfWeek() - 1)); transactionProxyModel->setDateRange( QDateTime(startOfWeek), TransactionFilterProxy::MAX_DATE); } break; case ThisMonth: transactionProxyModel->setDateRange( QDateTime(QDate(current.year(), current.month(), 1)), TransactionFilterProxy::MAX_DATE); break; case LastMonth: transactionProxyModel->setDateRange( QDateTime( QDate(current.year(), current.month(), 1).addMonths(-1)), QDateTime(QDate(current.year(), current.month(), 1))); break; case ThisYear: transactionProxyModel->setDateRange( QDateTime(QDate(current.year(), 1, 1)), TransactionFilterProxy::MAX_DATE); break; case Range: dateRangeWidget->setVisible(true); dateRangeChanged(); break; } } void TransactionView::chooseType(int idx) { if (!transactionProxyModel) return; transactionProxyModel->setTypeFilter(typeWidget->itemData(idx).toInt()); } void TransactionView::chooseWatchonly(int idx) { if (!transactionProxyModel) return; transactionProxyModel->setWatchOnlyFilter( (TransactionFilterProxy::WatchOnlyFilter)watchOnlyWidget->itemData(idx) .toInt()); } void TransactionView::changedPrefix(const QString &prefix) { if (!transactionProxyModel) return; transactionProxyModel->setAddressPrefix(prefix); } void TransactionView::changedAmount(const QString &amount) { if (!transactionProxyModel) return; - CAmount amount_parsed(0); + Amount amount_parsed(0); if (BitcoinUnits::parse(model->getOptionsModel()->getDisplayUnit(), amount, &amount_parsed)) { - transactionProxyModel->setMinAmount(amount_parsed); + transactionProxyModel->setMinAmount(amount_parsed.GetSatoshis()); } else { transactionProxyModel->setMinAmount(0); } } void TransactionView::exportClicked() { // CSV is currently the only supported format QString filename = GUIUtil::getSaveFileName( this, tr("Export Transaction History"), QString(), tr("Comma separated file (*.csv)"), nullptr); if (filename.isNull()) return; CSVModelWriter writer(filename); // name, column, role writer.setModel(transactionProxyModel); writer.addColumn(tr("Confirmed"), 0, TransactionTableModel::ConfirmedRole); if (model && model->haveWatchOnly()) writer.addColumn(tr("Watch-only"), TransactionTableModel::Watchonly); writer.addColumn(tr("Date"), 0, TransactionTableModel::DateRole); writer.addColumn(tr("Type"), TransactionTableModel::Type, Qt::EditRole); writer.addColumn(tr("Label"), 0, TransactionTableModel::LabelRole); writer.addColumn(tr("Address"), 0, TransactionTableModel::AddressRole); writer.addColumn(BitcoinUnits::getAmountColumnTitle( model->getOptionsModel()->getDisplayUnit()), 0, TransactionTableModel::FormattedAmountRole); writer.addColumn(tr("ID"), 0, TransactionTableModel::TxIDRole); if (!writer.write()) { Q_EMIT message(tr("Exporting Failed"), tr("There was an error trying to save the transaction " "history to %1.") .arg(filename), CClientUIInterface::MSG_ERROR); } else { Q_EMIT message( tr("Exporting Successful"), tr("The transaction history was successfully saved to %1.") .arg(filename), CClientUIInterface::MSG_INFORMATION); } } void TransactionView::contextualMenu(const QPoint &point) { QModelIndex index = transactionView->indexAt(point); QModelIndexList selection = transactionView->selectionModel()->selectedRows(0); if (selection.empty()) return; // check if transaction can be abandoned, disable context menu action in // case it doesn't uint256 hash; hash.SetHex(selection.at(0) .data(TransactionTableModel::TxHashRole) .toString() .toStdString()); abandonAction->setEnabled(model->transactionCanBeAbandoned(hash)); if (index.isValid()) { contextMenu->exec(QCursor::pos()); } } void TransactionView::abandonTx() { if (!transactionView || !transactionView->selectionModel()) return; QModelIndexList selection = transactionView->selectionModel()->selectedRows(0); // get the hash from the TxHashRole (QVariant / QString) uint256 hash; QString hashQStr = selection.at(0).data(TransactionTableModel::TxHashRole).toString(); hash.SetHex(hashQStr.toStdString()); // Abandon the wallet transaction over the walletModel model->abandonTransaction(hash); // Update the table model->getTransactionTableModel()->updateTransaction(hashQStr, CT_UPDATED, false); } void TransactionView::copyAddress() { GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::AddressRole); } void TransactionView::copyLabel() { GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::LabelRole); } void TransactionView::copyAmount() { GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::FormattedAmountRole); } void TransactionView::copyTxID() { GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxIDRole); } void TransactionView::copyTxHex() { GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxHexRole); } void TransactionView::copyTxPlainText() { GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxPlainTextRole); } void TransactionView::editLabel() { if (!transactionView->selectionModel() || !model) return; QModelIndexList selection = transactionView->selectionModel()->selectedRows(); if (!selection.isEmpty()) { AddressTableModel *addressBook = model->getAddressTableModel(); if (!addressBook) return; QString address = selection.at(0).data(TransactionTableModel::AddressRole).toString(); if (address.isEmpty()) { // If this transaction has no associated address, exit return; } // Is address in address book? Address book can miss address when a // transaction is // sent from outside the UI. int idx = addressBook->lookupAddress(address); if (idx != -1) { // Edit sending / receiving address QModelIndex modelIdx = addressBook->index(idx, 0, QModelIndex()); // Determine type of address, launch appropriate editor dialog type QString type = modelIdx.data(AddressTableModel::TypeRole).toString(); EditAddressDialog dlg(type == AddressTableModel::Receive ? EditAddressDialog::EditReceivingAddress : EditAddressDialog::EditSendingAddress, this); dlg.setModel(addressBook); dlg.loadRow(idx); dlg.exec(); } else { // Add sending address EditAddressDialog dlg(EditAddressDialog::NewSendingAddress, this); dlg.setModel(addressBook); dlg.setAddress(address); dlg.exec(); } } } void TransactionView::showDetails() { if (!transactionView->selectionModel()) return; QModelIndexList selection = transactionView->selectionModel()->selectedRows(); if (!selection.isEmpty()) { TransactionDescDialog *dlg = new TransactionDescDialog(selection.at(0)); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->show(); } } void TransactionView::openThirdPartyTxUrl(QString url) { if (!transactionView || !transactionView->selectionModel()) return; QModelIndexList selection = transactionView->selectionModel()->selectedRows(0); if (!selection.isEmpty()) QDesktopServices::openUrl(QUrl::fromUserInput( url.replace("%s", selection.at(0) .data(TransactionTableModel::TxHashRole) .toString()))); } QWidget *TransactionView::createDateRangeWidget() { dateRangeWidget = new QFrame(); dateRangeWidget->setFrameStyle(QFrame::Panel | QFrame::Raised); dateRangeWidget->setContentsMargins(1, 1, 1, 1); QHBoxLayout *layout = new QHBoxLayout(dateRangeWidget); layout->setContentsMargins(0, 0, 0, 0); layout->addSpacing(23); layout->addWidget(new QLabel(tr("Range:"))); dateFrom = new QDateTimeEdit(this); dateFrom->setDisplayFormat("dd/MM/yy"); dateFrom->setCalendarPopup(true); dateFrom->setMinimumWidth(100); dateFrom->setDate(QDate::currentDate().addDays(-7)); layout->addWidget(dateFrom); layout->addWidget(new QLabel(tr("to"))); dateTo = new QDateTimeEdit(this); dateTo->setDisplayFormat("dd/MM/yy"); dateTo->setCalendarPopup(true); dateTo->setMinimumWidth(100); dateTo->setDate(QDate::currentDate()); layout->addWidget(dateTo); layout->addStretch(); // Hide by default dateRangeWidget->setVisible(false); // Notify on change connect(dateFrom, SIGNAL(dateChanged(QDate)), this, SLOT(dateRangeChanged())); connect(dateTo, SIGNAL(dateChanged(QDate)), this, SLOT(dateRangeChanged())); return dateRangeWidget; } void TransactionView::dateRangeChanged() { if (!transactionProxyModel) return; transactionProxyModel->setDateRange(QDateTime(dateFrom->date()), QDateTime(dateTo->date()).addDays(1)); } void TransactionView::focusTransaction(const QModelIndex &idx) { if (!transactionProxyModel) return; QModelIndex targetIdx = transactionProxyModel->mapFromSource(idx); transactionView->scrollTo(targetIdx); transactionView->setCurrentIndex(targetIdx); transactionView->setFocus(); } // We override the virtual resizeEvent of the QWidget to adjust tables column // sizes as the tables width is proportional to the dialogs width. void TransactionView::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); 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(event); if (ke->key() == Qt::Key_C && ke->modifiers().testFlag(Qt::ControlModifier)) { GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxPlainTextRole); return true; } } return QWidget::eventFilter(obj, event); } // show/hide column Watch-only void TransactionView::updateWatchOnlyColumn(bool fHaveWatchOnly) { watchOnlyWidget->setVisible(fHaveWatchOnly); transactionView->setColumnHidden(TransactionTableModel::Watchonly, !fHaveWatchOnly); }