From 9ca8b78544b413ed875ff9bb6ef376e21922e6ac Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 30 Sep 2013 00:30:12 +0400 Subject: [PATCH] Merge coin control features Read this topic for additional info: https://bitcointalk.org/index.php?topic=144331.0 --- novacoin-qt.pro | 6 + src/coincontrol.h | 57 +++ src/main.cpp | 5 +- src/main.h | 2 +- src/qt/coincontroldialog.cpp | 738 +++++++++++++++++++++++++++++++++++++ src/qt/coincontroldialog.h | 92 +++++ src/qt/coincontroltreewidget.cpp | 28 ++ src/qt/coincontroltreewidget.h | 17 + src/qt/forms/coincontroldialog.ui | 549 +++++++++++++++++++++++++++ src/qt/forms/optionsdialog.ui | 10 + src/qt/forms/sendcoinsdialog.ui | 609 ++++++++++++++++++++++++++++++- src/qt/locale/bitcoin_en.ts | 667 ++++++++++++++++++++++++++++------ src/qt/locale/bitcoin_ru.ts | 667 ++++++++++++++++++++++++++++------ src/qt/optionsdialog.cpp | 1 + src/qt/optionsmodel.cpp | 15 + src/qt/optionsmodel.h | 5 + src/qt/sendcoinsdialog.cpp | 218 +++++++++++- src/qt/sendcoinsdialog.h | 14 + src/qt/sendcoinsentry.cpp | 2 + src/qt/sendcoinsentry.h | 1 + src/qt/walletmodel.cpp | 84 ++++- src/qt/walletmodel.h | 20 +- src/wallet.cpp | 67 +++-- src/wallet.h | 9 +- 24 files changed, 3616 insertions(+), 267 deletions(-) create mode 100644 src/coincontrol.h create mode 100644 src/qt/coincontroldialog.cpp create mode 100644 src/qt/coincontroldialog.h create mode 100644 src/qt/coincontroltreewidget.cpp create mode 100644 src/qt/coincontroltreewidget.h create mode 100644 src/qt/forms/coincontroldialog.ui diff --git a/novacoin-qt.pro b/novacoin-qt.pro index d959efe..386d1fd 100644 --- a/novacoin-qt.pro +++ b/novacoin-qt.pro @@ -153,6 +153,8 @@ HEADERS += src/qt/bitcoingui.h \ src/qt/transactiontablemodel.h \ src/qt/addresstablemodel.h \ src/qt/optionsdialog.h \ + src/qt/coincontroldialog.h \ + src/qt/coincontroltreewidget.h \ src/qt/sendcoinsdialog.h \ src/qt/addressbookpage.h \ src/qt/signverifymessagedialog.h \ @@ -165,6 +167,7 @@ HEADERS += src/qt/bitcoingui.h \ src/bignum.h \ src/checkpoints.h \ src/compat.h \ + src/coincontrol.h \ src/sync.h \ src/util.h \ src/uint256.h \ @@ -242,6 +245,8 @@ SOURCES += src/qt/bitcoin.cpp src/qt/bitcoingui.cpp \ src/qt/addresstablemodel.cpp \ src/qt/optionsdialog.cpp \ src/qt/sendcoinsdialog.cpp \ + src/qt/coincontroldialog.cpp \ + src/qt/coincontroltreewidget.cpp \ src/qt/addressbookpage.cpp \ src/qt/signverifymessagedialog.cpp \ src/qt/aboutdialog.cpp \ @@ -318,6 +323,7 @@ RESOURCES += \ src/qt/bitcoin.qrc FORMS += \ + src/qt/forms/coincontroldialog.ui \ src/qt/forms/sendcoinsdialog.ui \ src/qt/forms/addressbookpage.ui \ src/qt/forms/signverifymessagedialog.ui \ diff --git a/src/coincontrol.h b/src/coincontrol.h new file mode 100644 index 0000000..236b586 --- /dev/null +++ b/src/coincontrol.h @@ -0,0 +1,57 @@ +#ifndef COINCONTROL_H +#define COINCONTROL_H + +/** Coin Control Features. */ +class CCoinControl +{ +public: + CTxDestination destChange; + + CCoinControl() + { + SetNull(); + } + + void SetNull() + { + destChange = CNoDestination(); + setSelected.clear(); + } + + bool HasSelected() const + { + return (setSelected.size() > 0); + } + + bool IsSelected(const uint256& hash, unsigned int n) const + { + COutPoint outpt(hash, n); + return (setSelected.count(outpt) > 0); + } + + void Select(COutPoint& output) + { + setSelected.insert(output); + } + + void UnSelect(COutPoint& output) + { + setSelected.erase(output); + } + + void UnSelectAll() + { + setSelected.clear(); + } + + void ListSelected(std::vector& vOutpoints) + { + vOutpoints.assign(setSelected.begin(), setSelected.end()); + } + +private: + std::set setSelected; + +}; + +#endif // COINCONTROL_H diff --git a/src/main.cpp b/src/main.cpp index 68190ad..14339a3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -511,12 +511,11 @@ bool CTransaction::CheckTransaction() const } int64 CTransaction::GetMinFee(unsigned int nBlockSize, bool fAllowFree, - enum GetMinFee_mode mode) const + enum GetMinFee_mode mode, unsigned int nBytes) const { // Base fee is either MIN_TX_FEE or MIN_RELAY_TX_FEE int64 nBaseFee = (mode == GMF_RELAY) ? MIN_RELAY_TX_FEE : MIN_TX_FEE; - unsigned int nBytes = ::GetSerializeSize(*this, SER_NETWORK, PROTOCOL_VERSION); unsigned int nNewBlockSize = nBlockSize + nBytes; int64 nMinFee = (1 + (int64)nBytes / 1000) * nBaseFee; @@ -632,7 +631,7 @@ bool CTxMemPool::accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs, unsigned int nSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); // Don't accept it if it can't get into a block - int64 txMinFee = tx.GetMinFee(1000, false, GMF_RELAY); + int64 txMinFee = tx.GetMinFee(1000, false, GMF_RELAY, nSize); if (nFees < txMinFee) return error("CTxMemPool::accept() : not enough fees %s, %"PRI64d" < %"PRI64d, hash.ToString().c_str(), diff --git a/src/main.h b/src/main.h index 7a655a4..9680bee 100644 --- a/src/main.h +++ b/src/main.h @@ -592,7 +592,7 @@ public: return dPriority > COIN * 144 / 250; } - int64 GetMinFee(unsigned int nBlockSize=1, bool fAllowFree=false, enum GetMinFee_mode mode=GMF_BLOCK) const; + int64 GetMinFee(unsigned int nBlockSize=1, bool fAllowFree=false, enum GetMinFee_mode mode=GMF_BLOCK, unsigned int nBytes = 0) const; bool ReadFromDisk(CDiskTxPos pos, FILE** pfileRet=NULL) { diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp new file mode 100644 index 0000000..2df3c64 --- /dev/null +++ b/src/qt/coincontroldialog.cpp @@ -0,0 +1,738 @@ +#include "coincontroldialog.h" +#include "ui_coincontroldialog.h" + +#include "init.h" +#include "bitcoinunits.h" +#include "walletmodel.h" +#include "addresstablemodel.h" +#include "optionsmodel.h" +#include "coincontrol.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +QList CoinControlDialog::payAmounts; +CCoinControl* CoinControlDialog::coinControl = new CCoinControl(); + +CoinControlDialog::CoinControlDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::CoinControlDialog), + model(0) +{ + ui->setupUi(this); + + // context menu actions + QAction *copyAddressAction = new QAction(tr("Copy address"), this); + QAction *copyLabelAction = new QAction(tr("Copy label"), this); + QAction *copyAmountAction = new QAction(tr("Copy amount"), this); + copyTransactionHashAction = new QAction(tr("Copy transaction ID"), this); // we need to enable/disable this + //lockAction = new QAction(tr("Lock unspent"), this); // we need to enable/disable this + //unlockAction = new QAction(tr("Unlock unspent"), this); // we need to enable/disable this + + // context menu + contextMenu = new QMenu(); + contextMenu->addAction(copyAddressAction); + contextMenu->addAction(copyLabelAction); + contextMenu->addAction(copyAmountAction); + contextMenu->addAction(copyTransactionHashAction); + //contextMenu->addSeparator(); + //contextMenu->addAction(lockAction); + //contextMenu->addAction(unlockAction); + + // context menu signals + connect(ui->treeWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showMenu(QPoint))); + connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(copyAddress())); + connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel())); + connect(copyAmountAction, SIGNAL(triggered()), this, SLOT(copyAmount())); + connect(copyTransactionHashAction, SIGNAL(triggered()), this, SLOT(copyTransactionHash())); + //connect(lockAction, SIGNAL(triggered()), this, SLOT(lockCoin())); + //connect(unlockAction, SIGNAL(triggered()), this, SLOT(unlockCoin())); + + // clipboard actions + QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this); + QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this); + QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this); + QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this); + QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this); + QAction *clipboardPriorityAction = new QAction(tr("Copy priority"), this); + QAction *clipboardLowOutputAction = new QAction(tr("Copy low output"), this); + QAction *clipboardChangeAction = new QAction(tr("Copy change"), this); + + connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(clipboardQuantity())); + connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(clipboardAmount())); + connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(clipboardFee())); + connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(clipboardAfterFee())); + connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(clipboardBytes())); + connect(clipboardPriorityAction, SIGNAL(triggered()), this, SLOT(clipboardPriority())); + connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(clipboardLowOutput())); + connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(clipboardChange())); + + ui->labelCoinControlQuantity->addAction(clipboardQuantityAction); + ui->labelCoinControlAmount->addAction(clipboardAmountAction); + ui->labelCoinControlFee->addAction(clipboardFeeAction); + ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction); + ui->labelCoinControlBytes->addAction(clipboardBytesAction); + ui->labelCoinControlPriority->addAction(clipboardPriorityAction); + ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction); + ui->labelCoinControlChange->addAction(clipboardChangeAction); + + // toggle tree/list mode + connect(ui->radioTreeMode, SIGNAL(toggled(bool)), this, SLOT(radioTreeMode(bool))); + connect(ui->radioListMode, SIGNAL(toggled(bool)), this, SLOT(radioListMode(bool))); + + // click on checkbox + connect(ui->treeWidget, SIGNAL(itemChanged( QTreeWidgetItem*, int)), this, SLOT(viewItemChanged( QTreeWidgetItem*, int))); + + // click on header + ui->treeWidget->header()->setClickable(true); + connect(ui->treeWidget->header(), SIGNAL(sectionClicked(int)), this, SLOT(headerSectionClicked(int))); + + // ok button + connect(ui->buttonBox, SIGNAL(clicked( QAbstractButton*)), this, SLOT(buttonBoxClicked(QAbstractButton*))); + + // (un)select all + connect(ui->pushButtonSelectAll, SIGNAL(clicked()), this, SLOT(buttonSelectAllClicked())); + + ui->treeWidget->setColumnWidth(COLUMN_CHECKBOX, 84); + ui->treeWidget->setColumnWidth(COLUMN_AMOUNT, 100); + ui->treeWidget->setColumnWidth(COLUMN_LABEL, 170); + ui->treeWidget->setColumnWidth(COLUMN_ADDRESS, 290); + ui->treeWidget->setColumnWidth(COLUMN_DATE, 110); + ui->treeWidget->setColumnWidth(COLUMN_CONFIRMATIONS, 100); + ui->treeWidget->setColumnWidth(COLUMN_PRIORITY, 100); + ui->treeWidget->setColumnHidden(COLUMN_TXHASH, true); // store transacton hash in this column, but dont show it + ui->treeWidget->setColumnHidden(COLUMN_VOUT_INDEX, true); // store vout index in this column, but dont show it + ui->treeWidget->setColumnHidden(COLUMN_AMOUNT_INT64, true); // store amount int64 in this column, but dont show it + ui->treeWidget->setColumnHidden(COLUMN_PRIORITY_INT64, true); // store priority int64 in this column, but dont show it + + // default view is sorted by amount desc + sortView(COLUMN_AMOUNT_INT64, Qt::DescendingOrder); +} + +CoinControlDialog::~CoinControlDialog() +{ + delete ui; +} + +void CoinControlDialog::setModel(WalletModel *model) +{ + this->model = model; + + if(model && model->getOptionsModel() && model->getAddressTableModel()) + { + updateView(); + //updateLabelLocked(); + CoinControlDialog::updateLabels(model, this); + } +} + +// helper function str_pad +QString CoinControlDialog::strPad(QString s, int nPadLength, QString sPadding) +{ + while (s.length() < nPadLength) + s = sPadding + s; + + return s; +} + +// ok button +void CoinControlDialog::buttonBoxClicked(QAbstractButton* button) +{ + if (ui->buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) + done(QDialog::Accepted); // closes the dialog +} + +// (un)select all +void CoinControlDialog::buttonSelectAllClicked() +{ + Qt::CheckState state = Qt::Checked; + for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) + { + if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != Qt::Unchecked) + { + state = Qt::Unchecked; + break; + } + } + ui->treeWidget->setEnabled(false); + for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) + if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != state) + ui->treeWidget->topLevelItem(i)->setCheckState(COLUMN_CHECKBOX, state); + ui->treeWidget->setEnabled(true); + CoinControlDialog::updateLabels(model, this); +} + +// context menu +void CoinControlDialog::showMenu(const QPoint &point) +{ + QTreeWidgetItem *item = ui->treeWidget->itemAt(point); + if(item) + { + contextMenuItem = item; + + // disable some items (like Copy Transaction ID, lock, unlock) for tree roots in context menu + if (item->text(COLUMN_TXHASH).length() == 64) // transaction hash is 64 characters (this means its a child node, so its not a parent node in tree mode) + { + copyTransactionHashAction->setEnabled(true); + //if (model->isLockedCoin(uint256(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt())) + //{ + // lockAction->setEnabled(false); + // unlockAction->setEnabled(true); + //} + //else + //{ + // lockAction->setEnabled(true); + // unlockAction->setEnabled(false); + //} + } + else // this means click on parent node in tree mode -> disable all + { + copyTransactionHashAction->setEnabled(false); + //lockAction->setEnabled(false); + //unlockAction->setEnabled(false); + } + + // show context menu + contextMenu->exec(QCursor::pos()); + } +} + +// context menu action: copy amount +void CoinControlDialog::copyAmount() +{ + QApplication::clipboard()->setText(contextMenuItem->text(COLUMN_AMOUNT)); +} + +// context menu action: copy label +void CoinControlDialog::copyLabel() +{ + if (ui->radioTreeMode->isChecked() && contextMenuItem->text(COLUMN_LABEL).length() == 0 && contextMenuItem->parent()) + QApplication::clipboard()->setText(contextMenuItem->parent()->text(COLUMN_LABEL)); + else + QApplication::clipboard()->setText(contextMenuItem->text(COLUMN_LABEL)); +} + +// context menu action: copy address +void CoinControlDialog::copyAddress() +{ + if (ui->radioTreeMode->isChecked() && contextMenuItem->text(COLUMN_ADDRESS).length() == 0 && contextMenuItem->parent()) + QApplication::clipboard()->setText(contextMenuItem->parent()->text(COLUMN_ADDRESS)); + else + QApplication::clipboard()->setText(contextMenuItem->text(COLUMN_ADDRESS)); +} + +// context menu action: copy transaction id +void CoinControlDialog::copyTransactionHash() +{ + QApplication::clipboard()->setText(contextMenuItem->text(COLUMN_TXHASH)); +} + +// context menu action: lock coin +/*void CoinControlDialog::lockCoin() +{ + if (contextMenuItem->checkState(COLUMN_CHECKBOX) == Qt::Checked) + contextMenuItem->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); + + COutPoint outpt(uint256(contextMenuItem->text(COLUMN_TXHASH).toStdString()), contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt()); + model->lockCoin(outpt); + contextMenuItem->setDisabled(true); + contextMenuItem->setIcon(COLUMN_CHECKBOX, QIcon(":/icons/lock_closed")); + updateLabelLocked(); +}*/ + +// context menu action: unlock coin +/*void CoinControlDialog::unlockCoin() +{ + COutPoint outpt(uint256(contextMenuItem->text(COLUMN_TXHASH).toStdString()), contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt()); + model->unlockCoin(outpt); + contextMenuItem->setDisabled(false); + contextMenuItem->setIcon(COLUMN_CHECKBOX, QIcon()); + updateLabelLocked(); +}*/ + +// copy label "Quantity" to clipboard +void CoinControlDialog::clipboardQuantity() +{ + QApplication::clipboard()->setText(ui->labelCoinControlQuantity->text()); +} + +// copy label "Amount" to clipboard +void CoinControlDialog::clipboardAmount() +{ + QApplication::clipboard()->setText(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" "))); +} + +// copy label "Fee" to clipboard +void CoinControlDialog::clipboardFee() +{ + QApplication::clipboard()->setText(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" "))); +} + +// copy label "After fee" to clipboard +void CoinControlDialog::clipboardAfterFee() +{ + QApplication::clipboard()->setText(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" "))); +} + +// copy label "Bytes" to clipboard +void CoinControlDialog::clipboardBytes() +{ + QApplication::clipboard()->setText(ui->labelCoinControlBytes->text()); +} + +// copy label "Priority" to clipboard +void CoinControlDialog::clipboardPriority() +{ + QApplication::clipboard()->setText(ui->labelCoinControlPriority->text()); +} + +// copy label "Low output" to clipboard +void CoinControlDialog::clipboardLowOutput() +{ + QApplication::clipboard()->setText(ui->labelCoinControlLowOutput->text()); +} + +// copy label "Change" to clipboard +void CoinControlDialog::clipboardChange() +{ + QApplication::clipboard()->setText(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" "))); +} + +// treeview: sort +void CoinControlDialog::sortView(int column, Qt::SortOrder order) +{ + sortColumn = column; + sortOrder = order; + ui->treeWidget->sortItems(column, order); + ui->treeWidget->header()->setSortIndicator((sortColumn == COLUMN_AMOUNT_INT64 ? COLUMN_AMOUNT : (sortColumn == COLUMN_PRIORITY_INT64 ? COLUMN_PRIORITY : sortColumn)), sortOrder); +} + +// treeview: clicked on header +void CoinControlDialog::headerSectionClicked(int logicalIndex) +{ + if (logicalIndex == COLUMN_CHECKBOX) // click on most left column -> do nothing + { + ui->treeWidget->header()->setSortIndicator((sortColumn == COLUMN_AMOUNT_INT64 ? COLUMN_AMOUNT : (sortColumn == COLUMN_PRIORITY_INT64 ? COLUMN_PRIORITY : sortColumn)), sortOrder); + } + else + { + if (logicalIndex == COLUMN_AMOUNT) // sort by amount + logicalIndex = COLUMN_AMOUNT_INT64; + + if (logicalIndex == COLUMN_PRIORITY) // sort by priority + logicalIndex = COLUMN_PRIORITY_INT64; + + if (sortColumn == logicalIndex) + sortOrder = ((sortOrder == Qt::AscendingOrder) ? Qt::DescendingOrder : Qt::AscendingOrder); + else + { + sortColumn = logicalIndex; + sortOrder = ((sortColumn == COLUMN_AMOUNT_INT64 || sortColumn == COLUMN_PRIORITY_INT64 || sortColumn == COLUMN_DATE || sortColumn == COLUMN_CONFIRMATIONS) ? Qt::DescendingOrder : Qt::AscendingOrder); // if amount,date,conf,priority then default => desc, else default => asc + } + + sortView(sortColumn, sortOrder); + } +} + +// toggle tree mode +void CoinControlDialog::radioTreeMode(bool checked) +{ + if (checked && model) + updateView(); +} + +// toggle list mode +void CoinControlDialog::radioListMode(bool checked) +{ + if (checked && model) + updateView(); +} + +// checkbox clicked by user +void CoinControlDialog::viewItemChanged(QTreeWidgetItem* item, int column) +{ + if (column == COLUMN_CHECKBOX && item->text(COLUMN_TXHASH).length() == 64) // transaction hash is 64 characters (this means its a child node, so its not a parent node in tree mode) + { + COutPoint outpt(uint256(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt()); + + if (item->checkState(COLUMN_CHECKBOX) == Qt::Unchecked) + coinControl->UnSelect(outpt); + else if (item->isDisabled()) // locked (this happens if "check all" through parent node) + item->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); + else + coinControl->Select(outpt); + + // selection changed -> update labels + if (ui->treeWidget->isEnabled()) // do not update on every click for (un)select all + CoinControlDialog::updateLabels(model, this); + } +} + +// helper function, return human readable label for priority number +QString CoinControlDialog::getPriorityLabel(double dPriority) +{ + if (dPriority > 576000ULL) // at least medium, this number is from AllowFree(), the other thresholds are kinda random + { + if (dPriority > 5760000000ULL) return tr("highest"); + else if (dPriority > 576000000ULL) return tr("high"); + else if (dPriority > 57600000ULL) return tr("medium-high"); + else return tr("medium"); + } + else + { + if (dPriority > 5760ULL) return tr("low-medium"); + else if (dPriority > 58ULL) return tr("low"); + else return tr("lowest"); + } +} + +// shows count of locked unspent outputs +/*void CoinControlDialog::updateLabelLocked() +{ + vector vOutpts; + model->listLockedCoins(vOutpts); + if (vOutpts.size() > 0) + { + ui->labelLocked->setText(tr("(%1 locked)").arg(vOutpts.size())); + ui->labelLocked->setVisible(true); + } + else ui->labelLocked->setVisible(false); +}*/ + +void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) +{ + if (!model) return; + + // nPayAmount + qint64 nPayAmount = 0; + bool fLowOutput = false; + bool fDust = false; + CTransaction txDummy; + foreach(const qint64 &amount, CoinControlDialog::payAmounts) + { + nPayAmount += amount; + + if (amount > 0) + { + if (amount < CENT) + fLowOutput = true; + + CTxOut txout(amount, (CScript)vector(24, 0)); + txDummy.vout.push_back(txout); + } + } + + QString sPriorityLabel = ""; + int64 nAmount = 0; + int64 nPayFee = 0; + int64 nAfterFee = 0; + int64 nChange = 0; + unsigned int nBytes = 0; + unsigned int nBytesInputs = 0; + double dPriority = 0; + double dPriorityInputs = 0; + unsigned int nQuantity = 0; + + vector vCoinControl; + vector vOutputs; + coinControl->ListSelected(vCoinControl); + model->getOutputs(vCoinControl, vOutputs); + + BOOST_FOREACH(const COutput& out, vOutputs) + { + // Quantity + nQuantity++; + + // Amount + nAmount += out.tx->vout[out.i].nValue; + + // Priority + dPriorityInputs += (double)out.tx->vout[out.i].nValue * (out.nDepth+1); + + // Bytes + CTxDestination address; + if(ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) + { + CPubKey pubkey; + CKeyID *keyid = boost::get< CKeyID >(&address); + if (keyid && model->getPubKey(*keyid, pubkey)) + nBytesInputs += (pubkey.IsCompressed() ? 148 : 180); + else + nBytesInputs += 148; // in all error cases, simply assume 148 here + } + else nBytesInputs += 148; + } + + // calculation + if (nQuantity > 0) + { + // Bytes + nBytes = nBytesInputs + ((CoinControlDialog::payAmounts.size() > 0 ? CoinControlDialog::payAmounts.size() + 1 : 2) * 34) + 10; // always assume +1 output for change here + + // Priority + dPriority = dPriorityInputs / nBytes; + sPriorityLabel = CoinControlDialog::getPriorityLabel(dPriority); + + // Fee + int64 nFee = nTransactionFee * (1 + (int64)nBytes / 1000); + + // Min Fee + int64 nMinFee = txDummy.GetMinFee(1, false, GMF_SEND, nBytes); + + nPayFee = max(nFee, nMinFee); + + if (nPayAmount > 0) + { + nChange = nAmount - nPayFee - nPayAmount; + + // if sub-cent change is required, the fee must be raised to at least CTransaction::nMinTxFee + if (nPayFee < CENT && nChange > 0 && nChange < CENT) + { + if (nChange < CENT) // change < 0.01 => simply move all change to fees + { + nPayFee = nChange; + nChange = 0; + } + else + { + nChange = nChange + nPayFee - CENT; + nPayFee = CENT; + } + } + + if (nChange == 0) + nBytes -= 34; + } + + // after fee + nAfterFee = nAmount - nPayFee; + if (nAfterFee < 0) + nAfterFee = 0; + } + + // actually update labels + int nDisplayUnit = BitcoinUnits::BTC; + if (model && model->getOptionsModel()) + nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); + + QLabel *l1 = dialog->findChild("labelCoinControlQuantity"); + QLabel *l2 = dialog->findChild("labelCoinControlAmount"); + QLabel *l3 = dialog->findChild("labelCoinControlFee"); + QLabel *l4 = dialog->findChild("labelCoinControlAfterFee"); + QLabel *l5 = dialog->findChild("labelCoinControlBytes"); + QLabel *l6 = dialog->findChild("labelCoinControlPriority"); + QLabel *l7 = dialog->findChild("labelCoinControlLowOutput"); + QLabel *l8 = dialog->findChild("labelCoinControlChange"); + + // enable/disable "low output" and "change" + dialog->findChild("labelCoinControlLowOutputText")->setEnabled(nPayAmount > 0); + dialog->findChild("labelCoinControlLowOutput") ->setEnabled(nPayAmount > 0); + dialog->findChild("labelCoinControlChangeText") ->setEnabled(nPayAmount > 0); + dialog->findChild("labelCoinControlChange") ->setEnabled(nPayAmount > 0); + + // stats + l1->setText(QString::number(nQuantity)); // Quantity + l2->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nAmount)); // Amount + l3->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nPayFee)); // Fee + l4->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nAfterFee)); // After Fee + l5->setText(((nBytes > 0) ? "~" : "") + QString::number(nBytes)); // Bytes + l6->setText(sPriorityLabel); // Priority + l7->setText((fLowOutput ? (fDust ? tr("DUST") : tr("yes")) : tr("no"))); // Low Output / Dust + l8->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nChange)); // Change + + // turn labels "red" + l5->setStyleSheet((nBytes >= 10000) ? "color:red;" : ""); // Bytes >= 10000 + l6->setStyleSheet((dPriority <= 576000) ? "color:red;" : ""); // Priority < "medium" + l7->setStyleSheet((fLowOutput) ? "color:red;" : ""); // Low Output = "yes" + l8->setStyleSheet((nChange > 0 && nChange < CENT) ? "color:red;" : ""); // Change < 0.01BTC + + // tool tips + l5->setToolTip(tr("This label turns red, if the transaction size is bigger than 10000 bytes.\n\n This means a fee of at least %1 per kb is required.\n\n Can vary +/- 1 Byte per input.").arg(BitcoinUnits::formatWithUnit(nDisplayUnit, CENT))); + l6->setToolTip(tr("Transactions with higher priority get more likely into a block.\n\nThis label turns red, if the priority is smaller than \"medium\".\n\n This means a fee of at least %1 per kb is required.").arg(BitcoinUnits::formatWithUnit(nDisplayUnit, CENT))); + l7->setToolTip(tr("This label turns red, if any recipient receives an amount smaller than %1.\n\n This means a fee of at least %2 is required. \n\n Amounts below 0.546 times the minimum relay fee are shown as DUST.").arg(BitcoinUnits::formatWithUnit(nDisplayUnit, CENT)).arg(BitcoinUnits::formatWithUnit(nDisplayUnit, CENT))); + l8->setToolTip(tr("This label turns red, if the change is smaller than %1.\n\n This means a fee of at least %2 is required.").arg(BitcoinUnits::formatWithUnit(nDisplayUnit, CENT)).arg(BitcoinUnits::formatWithUnit(nDisplayUnit, CENT))); + dialog->findChild("labelCoinControlBytesText") ->setToolTip(l5->toolTip()); + dialog->findChild("labelCoinControlPriorityText") ->setToolTip(l6->toolTip()); + dialog->findChild("labelCoinControlLowOutputText")->setToolTip(l7->toolTip()); + dialog->findChild("labelCoinControlChangeText") ->setToolTip(l8->toolTip()); + + // Insufficient funds + QLabel *label = dialog->findChild("labelCoinControlInsuffFunds"); + if (label) + label->setVisible(nChange < 0); +} + +void CoinControlDialog::updateView() +{ + bool treeMode = ui->radioTreeMode->isChecked(); + + ui->treeWidget->clear(); + ui->treeWidget->setEnabled(false); // performance, otherwise updateLabels would be called for every checked checkbox + ui->treeWidget->setAlternatingRowColors(!treeMode); + QFlags flgCheckbox=Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; + QFlags flgTristate=Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsTristate; + + int nDisplayUnit = BitcoinUnits::BTC; + if (model && model->getOptionsModel()) + nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); + + map > mapCoins; + model->listCoins(mapCoins); + + BOOST_FOREACH(PAIRTYPE(QString, vector) coins, mapCoins) + { + QTreeWidgetItem *itemWalletAddress = new QTreeWidgetItem(); + QString sWalletAddress = coins.first; + QString sWalletLabel = ""; + if (model->getAddressTableModel()) + sWalletLabel = model->getAddressTableModel()->labelForAddress(sWalletAddress); + if (sWalletLabel.length() == 0) + sWalletLabel = tr("(no label)"); + + if (treeMode) + { + // wallet address + ui->treeWidget->addTopLevelItem(itemWalletAddress); + + itemWalletAddress->setFlags(flgTristate); + itemWalletAddress->setCheckState(COLUMN_CHECKBOX,Qt::Unchecked); + + for (int i = 0; i < ui->treeWidget->columnCount(); i++) + itemWalletAddress->setBackground(i, QColor(248, 247, 246)); + + // label + itemWalletAddress->setText(COLUMN_LABEL, sWalletLabel); + + // address + itemWalletAddress->setText(COLUMN_ADDRESS, sWalletAddress); + } + + int64 nSum = 0; + double dPrioritySum = 0; + int nChildren = 0; + int nInputSum = 0; + BOOST_FOREACH(const COutput& out, coins.second) + { + int nInputSize = 148; // 180 if uncompressed public key + nSum += out.tx->vout[out.i].nValue; + nChildren++; + + QTreeWidgetItem *itemOutput; + if (treeMode) itemOutput = new QTreeWidgetItem(itemWalletAddress); + else itemOutput = new QTreeWidgetItem(ui->treeWidget); + itemOutput->setFlags(flgCheckbox); + itemOutput->setCheckState(COLUMN_CHECKBOX,Qt::Unchecked); + + // address + CTxDestination outputAddress; + QString sAddress = ""; + if(ExtractDestination(out.tx->vout[out.i].scriptPubKey, outputAddress)) + { + sAddress = CBitcoinAddress(outputAddress).ToString().c_str(); + + // if listMode or change => show bitcoin address. In tree mode, address is not shown again for direct wallet address outputs + if (!treeMode || (!(sAddress == sWalletAddress))) + itemOutput->setText(COLUMN_ADDRESS, sAddress); + + CPubKey pubkey; + CKeyID *keyid = boost::get< CKeyID >(&outputAddress); + if (keyid && model->getPubKey(*keyid, pubkey) && !pubkey.IsCompressed()) + nInputSize = 180; + } + + // label + if (!(sAddress == sWalletAddress)) // change + { + // tooltip from where the change comes from + itemOutput->setToolTip(COLUMN_LABEL, tr("change from %1 (%2)").arg(sWalletLabel).arg(sWalletAddress)); + itemOutput->setText(COLUMN_LABEL, tr("(change)")); + } + else if (!treeMode) + { + QString sLabel = ""; + if (model->getAddressTableModel()) + sLabel = model->getAddressTableModel()->labelForAddress(sAddress); + if (sLabel.length() == 0) + sLabel = tr("(no label)"); + itemOutput->setText(COLUMN_LABEL, sLabel); + } + + // amount + itemOutput->setText(COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, out.tx->vout[out.i].nValue)); + itemOutput->setText(COLUMN_AMOUNT_INT64, strPad(QString::number(out.tx->vout[out.i].nValue), 15, " ")); // padding so that sorting works correctly + + // date + itemOutput->setText(COLUMN_DATE, QDateTime::fromTime_t(out.tx->GetTxTime()).toUTC().toString("yy-MM-dd hh:mm")); + + // immature PoS reward + if (out.tx->IsCoinStake() && out.tx->GetBlocksToMaturity() > 0 && out.tx->GetDepthInMainChain() > 0) { + itemOutput->setBackground(COLUMN_CONFIRMATIONS, Qt::red); + itemOutput->setDisabled(true); + } + + // confirmations + itemOutput->setText(COLUMN_CONFIRMATIONS, strPad(QString::number(out.nDepth), 8, " ")); + + // priority + double dPriority = ((double)out.tx->vout[out.i].nValue / (nInputSize + 78)) * (out.nDepth+1); // 78 = 2 * 34 + 10 + itemOutput->setText(COLUMN_PRIORITY, CoinControlDialog::getPriorityLabel(dPriority)); + itemOutput->setText(COLUMN_PRIORITY_INT64, strPad(QString::number((int64)dPriority), 20, " ")); + dPrioritySum += (double)out.tx->vout[out.i].nValue * (out.nDepth+1); + nInputSum += nInputSize; + + // transaction hash + uint256 txhash = out.tx->GetHash(); + itemOutput->setText(COLUMN_TXHASH, txhash.GetHex().c_str()); + + // vout index + itemOutput->setText(COLUMN_VOUT_INDEX, QString::number(out.i)); + + // disable locked coins + /*if (model->isLockedCoin(txhash, out.i)) + { + COutPoint outpt(txhash, out.i); + coinControl->UnSelect(outpt); // just to be sure + itemOutput->setDisabled(true); + itemOutput->setIcon(COLUMN_CHECKBOX, QIcon(":/icons/lock_closed")); + }*/ + + // set checkbox + if (coinControl->IsSelected(txhash, out.i)) + itemOutput->setCheckState(COLUMN_CHECKBOX,Qt::Checked); + } + + // amount + if (treeMode) + { + dPrioritySum = dPrioritySum / (nInputSum + 78); + itemWalletAddress->setText(COLUMN_CHECKBOX, "(" + QString::number(nChildren) + ")"); + itemWalletAddress->setText(COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, nSum)); + itemWalletAddress->setText(COLUMN_AMOUNT_INT64, strPad(QString::number(nSum), 15, " ")); + itemWalletAddress->setText(COLUMN_PRIORITY, CoinControlDialog::getPriorityLabel(dPrioritySum)); + itemWalletAddress->setText(COLUMN_PRIORITY_INT64, strPad(QString::number((int64)dPrioritySum), 20, " ")); + } + } + + // expand all partially selected + if (treeMode) + { + for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) + if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked) + ui->treeWidget->topLevelItem(i)->setExpanded(true); + } + + // sort view + sortView(sortColumn, sortOrder); + ui->treeWidget->setEnabled(true); +} diff --git a/src/qt/coincontroldialog.h b/src/qt/coincontroldialog.h new file mode 100644 index 0000000..5d0a90b --- /dev/null +++ b/src/qt/coincontroldialog.h @@ -0,0 +1,92 @@ +#ifndef COINCONTROLDIALOG_H +#define COINCONTROLDIALOG_H + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Ui { + class CoinControlDialog; +} +class WalletModel; +class CCoinControl; + +class CoinControlDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CoinControlDialog(QWidget *parent = 0); + ~CoinControlDialog(); + + void setModel(WalletModel *model); + + // static because also called from sendcoinsdialog + static void updateLabels(WalletModel*, QDialog*); + static QString getPriorityLabel(double); + + static QList payAmounts; + static CCoinControl *coinControl; + +private: + Ui::CoinControlDialog *ui; + WalletModel *model; + int sortColumn; + Qt::SortOrder sortOrder; + + QMenu *contextMenu; + QTreeWidgetItem *contextMenuItem; + QAction *copyTransactionHashAction; + //QAction *lockAction; + //QAction *unlockAction; + + QString strPad(QString, int, QString); + void sortView(int, Qt::SortOrder); + void updateView(); + + enum + { + COLUMN_CHECKBOX, + COLUMN_AMOUNT, + COLUMN_LABEL, + COLUMN_ADDRESS, + COLUMN_DATE, + COLUMN_CONFIRMATIONS, + COLUMN_PRIORITY, + COLUMN_TXHASH, + COLUMN_VOUT_INDEX, + COLUMN_AMOUNT_INT64, + COLUMN_PRIORITY_INT64 + }; + +private slots: + void showMenu(const QPoint &); + void copyAmount(); + void copyLabel(); + void copyAddress(); + void copyTransactionHash(); + //void lockCoin(); + //void unlockCoin(); + void clipboardQuantity(); + void clipboardAmount(); + void clipboardFee(); + void clipboardAfterFee(); + void clipboardBytes(); + void clipboardPriority(); + void clipboardLowOutput(); + void clipboardChange(); + void radioTreeMode(bool); + void radioListMode(bool); + void viewItemChanged(QTreeWidgetItem*, int); + void headerSectionClicked(int); + void buttonBoxClicked(QAbstractButton*); + void buttonSelectAllClicked(); + //void updateLabelLocked(); +}; + +#endif // COINCONTROLDIALOG_H diff --git a/src/qt/coincontroltreewidget.cpp b/src/qt/coincontroltreewidget.cpp new file mode 100644 index 0000000..aa75a49 --- /dev/null +++ b/src/qt/coincontroltreewidget.cpp @@ -0,0 +1,28 @@ +#include "coincontroltreewidget.h" +#include "coincontroldialog.h" + +CoinControlTreeWidget::CoinControlTreeWidget(QWidget *parent) : + QTreeWidget(parent) +{ + +} + +void CoinControlTreeWidget::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Space) // press spacebar -> select checkbox + { + event->ignore(); + int COLUMN_CHECKBOX = 0; + this->currentItem()->setCheckState(COLUMN_CHECKBOX, ((this->currentItem()->checkState(COLUMN_CHECKBOX) == Qt::Checked) ? Qt::Unchecked : Qt::Checked)); + } + else if (event->key() == Qt::Key_Escape) // press esc -> close dialog + { + event->ignore(); + CoinControlDialog *coinControlDialog = (CoinControlDialog*)this->parentWidget(); + coinControlDialog->done(QDialog::Accepted); + } + else + { + this->QTreeWidget::keyPressEvent(event); + } +} \ No newline at end of file diff --git a/src/qt/coincontroltreewidget.h b/src/qt/coincontroltreewidget.h new file mode 100644 index 0000000..d981f72 --- /dev/null +++ b/src/qt/coincontroltreewidget.h @@ -0,0 +1,17 @@ +#ifndef COINCONTROLTREEWIDGET_H +#define COINCONTROLTREEWIDGET_H + +#include +#include + +class CoinControlTreeWidget : public QTreeWidget { +Q_OBJECT + +public: + explicit CoinControlTreeWidget(QWidget *parent = 0); + +protected: + virtual void keyPressEvent(QKeyEvent *event); +}; + +#endif // COINCONTROLTREEWIDGET_H \ No newline at end of file diff --git a/src/qt/forms/coincontroldialog.ui b/src/qt/forms/coincontroldialog.ui new file mode 100644 index 0000000..c19e312 --- /dev/null +++ b/src/qt/forms/coincontroldialog.ui @@ -0,0 +1,549 @@ + + + CoinControlDialog + + + + 0 + 0 + 1000 + 500 + + + + Coin Control + + + + + + 0 + + + 10 + + + + + 10 + + + 10 + + + 6 + + + 6 + + + + + font-weight:bold; + + + Quantity: + + + + + + + + Monospace + 10 + + + + IBeamCursor + + + Qt::ActionsContextMenu + + + 0 + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + font-weight:bold; + + + Bytes: + + + + + + + + Monospace + 10 + + + + IBeamCursor + + + Qt::ActionsContextMenu + + + 0 + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + 10 + + + 10 + + + 6 + + + 6 + + + + + font-weight:bold; + + + Amount: + + + + + + + + Monospace + 10 + + + + IBeamCursor + + + Qt::ActionsContextMenu + + + 0.00 NVC + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + font-weight:bold; + + + Priority: + + + + + + + + Monospace + 10 + + + + IBeamCursor + + + Qt::ActionsContextMenu + + + + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + 10 + + + 10 + + + 6 + + + 6 + + + + + font-weight:bold; + + + Fee: + + + + + + + + Monospace + 10 + + + + IBeamCursor + + + Qt::ActionsContextMenu + + + 0.00 NVC + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + false + + + font-weight:bold; + + + Low Output: + + + + + + + false + + + + Monospace + 10 + + + + IBeamCursor + + + Qt::ActionsContextMenu + + + no + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + 10 + + + 10 + + + 6 + + + 6 + + + + + font-weight:bold; + + + After Fee: + + + + + + + + Monospace + 10 + + + + IBeamCursor + + + Qt::ActionsContextMenu + + + 0.00 NVC + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + false + + + font-weight:bold; + + + Change: + + + + + + + false + + + + Monospace + 10 + + + + IBeamCursor + + + Qt::ActionsContextMenu + + + 0.00 NVC + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + + + 0 + 40 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + 10 + 0 + 781 + 41 + + + + + 14 + + + + + + 0 + 0 + + + + (un)select all + + + + + + + + 0 + 0 + + + + Tree mode + + + true + + + + + + + + 0 + 0 + + + + List mode + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Qt::CustomContextMenu + + + false + + + 11 + + + true + + + false + + + + + + + + + Amount + + + + + Label + + + + + Address + + + + + Date + + + + + Confirmations + + + Confirmed + + + + + Priority + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + + CoinControlTreeWidget + QTreeWidget +
coincontroltreewidget.h
+
+
+ + +
diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui index d1284b6..47307bb 100644 --- a/src/qt/forms/optionsdialog.ui +++ b/src/qt/forms/optionsdialog.ui @@ -344,6 +344,16 @@ + + + Whether to show coin control features or not. + + + Display coin &control features (experts only!) + + + + Qt::Vertical diff --git a/src/qt/forms/sendcoinsdialog.ui b/src/qt/forms/sendcoinsdialog.ui index 6e17565..b1b0293 100644 --- a/src/qt/forms/sendcoinsdialog.ui +++ b/src/qt/forms/sendcoinsdialog.ui @@ -6,14 +6,615 @@ 0 0 - 686 - 217 + 850 + 400 Send Coins - + + + 8 + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + -1 + + + 0 + + + 0 + + + 0 + + + 6 + + + + + 0 + + + 10 + + + 10 + + + + + 15 + + + + + + 0 + 0 + + + + + 75 + true + + + + font-weight:bold; + + + Coin Control Features + + + + + + + + + 8 + + + 10 + + + + + + + + Inputs... + + + + + + + automatically selected + + + 5 + + + + + + + + 75 + true + + + + color:red;font-weight:bold; + + + Insufficient funds! + + + 5 + + + + + + + Qt::Horizontal + + + + 40 + 1 + + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + + + + 0 + + + + + 20 + + + 0 + + + 10 + + + + + 10 + + + 14 + + + 10 + + + 4 + + + 6 + + + + + font-weight:bold; + + + Quantity: + + + 0 + + + + + + + + Monospace + 10 + + + + IBeamCursor + + + Qt::ActionsContextMenu + + + 0 + + + 0 + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + font-weight:bold; + + + Bytes: + + + + + + + + Monospace + 10 + + + + IBeamCursor + + + Qt::ActionsContextMenu + + + 0 + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + 10 + + + 14 + + + 6 + + + 4 + + + 6 + + + + + font-weight:bold; + + + Amount: + + + 0 + + + + + + + + Monospace + 10 + + + + IBeamCursor + + + Qt::ActionsContextMenu + + + 0.00 BTC + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + font-weight:bold; + + + Priority: + + + + + + + + Monospace + 10 + + + + IBeamCursor + + + Qt::ActionsContextMenu + + + medium + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + 10 + + + 14 + + + 6 + + + 4 + + + 6 + + + + + font-weight:bold; + + + Fee: + + + 0 + + + + + + + + Monospace + 10 + + + + IBeamCursor + + + Qt::ActionsContextMenu + + + 0.00 BTC + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + font-weight:bold; + + + Low Output: + + + + + + + + Monospace + 10 + + + + IBeamCursor + + + Qt::ActionsContextMenu + + + no + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + 10 + + + 14 + + + 6 + + + 4 + + + 6 + + + + + font-weight:bold; + + + After Fee: + + + 0 + + + + + + + + Monospace + 10 + + + + IBeamCursor + + + Qt::ActionsContextMenu + + + 0.00 BTC + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + font-weight:bold; + + + Change + + + + + + + + Monospace + 10 + + + + IBeamCursor + + + Qt::ActionsContextMenu + + + 0.00 BTC + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + + + + + 12 + + + QLayout::SetDefaultConstraint + + + 5 + + + 5 + + + + + custom change address + + + + + + + false + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + + + 3 + + + + + + + + + Qt::Vertical + + + + 800 + 1 + + + + + + + + + @@ -24,7 +625,7 @@ 0 0 - 666 + 830 165 diff --git a/src/qt/locale/bitcoin_en.ts b/src/qt/locale/bitcoin_en.ts index a8f6a60..93e70da 100644 --- a/src/qt/locale/bitcoin_en.ts +++ b/src/qt/locale/bitcoin_en.ts @@ -663,12 +663,281 @@ Address: %4 ClientModel - + Network Alert Network Alert + CoinControlDialog + + + Coin Control + + + + + Quantity: + + + + + + 0 + + + + + Bytes: + + + + + Amount: + Amount: + + + + + + + 0.00 NVC + + + + + Priority: + + + + + Fee: + + + + + Low Output: + + + + + + no + + + + + After Fee: + + + + + Change: + + + + + (un)select all + + + + + Tree mode + + + + + List mode + + + + + Amount + Amount + + + + Label + Label + + + + Address + Address + + + + Date + Date + + + + Confirmations + + + + + Confirmed + Confirmed + + + + Priority + + + + + Copy address + Copy address + + + + Copy label + Copy label + + + + + Copy amount + Copy amount + + + + Copy transaction ID + Copy transaction ID + + + + Copy quantity + + + + + Copy fee + + + + + Copy after fee + + + + + Copy bytes + + + + + Copy priority + + + + + Copy low output + + + + + Copy change + + + + + highest + + + + + high + + + + + medium-high + + + + + medium + + + + + low-medium + + + + + low + + + + + lowest + + + + + DUST + + + + + yes + + + + + This label turns red, if the transaction size is bigger than 10000 bytes. + + This means a fee of at least %1 per kb is required. + + Can vary +/- 1 Byte per input. + + + + + Transactions with higher priority get more likely into a block. + +This label turns red, if the priority is smaller than "medium". + + This means a fee of at least %1 per kb is required. + + + + + This label turns red, if any recipient receives an amount smaller than %1. + + This means a fee of at least %2 is required. + + Amounts below 0.546 times the minimum relay fee are shown as DUST. + + + + + This label turns red, if the change is smaller than %1. + + This means a fee of at least %2 is required. + + + + + + (no label) + (no label) + + + + change from %1 (%2) + + + + + (change) + + + + EditAddressDialog @@ -739,7 +1008,7 @@ Address: %4 GUIUtil::HelpMessageBox - + NovaCoin-Qt NovaCoin-Qt @@ -938,6 +1207,16 @@ Address: %4 &Display addresses in transaction list + + Whether to show coin control features or not. + + + + + Display coin &control features (experts only!) + + + &OK &OK @@ -958,7 +1237,7 @@ Address: %4 default - + Warning Warning @@ -984,37 +1263,37 @@ Address: %4 - + The displayed information may be out of date. Your wallet automatically synchronizes with the NovaCoin network after a connection is established, but this process has not completed yet. The displayed information may be out of date. Your wallet automatically synchronizes with the NovaCoin network after a connection is established, but this process has not completed yet. - + Balance: Balance: - + Stake: Stake: - + Number of transactions: Number of transactions: - + Unconfirmed: Unconfirmed: - + Wallet Wallet - + Immature: Immature: @@ -1024,32 +1303,32 @@ Address: %4 Mined balance that has not yet matured - + <b>Recent transactions</b> <b>Recent transactions</b> - + Your current balance Your current balance - + Total of transactions that have yet to be confirmed, and do not yet count toward the current balance Total of transactions that have yet to be confirmed, and do not yet count toward the current balance - + Total of coins that was staked, and do not yet count toward the current balance Total of coins that was staked, and do not yet count toward the current balance - + Total number of transactions in wallet Total number of transactions in wallet - + out of sync out of sync @@ -1264,7 +1543,7 @@ Address: %4 SendCoinsDialog - + @@ -1275,7 +1554,96 @@ Address: %4 Send Coins + + Coin Control Features + + + + + Inputs... + + + + + automatically selected + + + + + Insufficient funds! + + + + + Quantity: + + + + + + 0 + + + + + Bytes: + + + + + Amount: + Amount: + + + + + + + 0.00 BTC + 123.456 BTC {0.00 ?} + + + + Priority: + + + + + medium + + + + + Fee: + + + + + Low Output: + + + + + no + + + + + After Fee: + + + + + Change + + + + custom change address + + + + Send to multiple recipients at once Send to multiple recipients at once @@ -1315,7 +1683,52 @@ Address: %4 - + + Enter a NovaCoin address (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) + Enter a NovaCoin address (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) + + + + Copy quantity + + + + + Copy amount + Copy amount + + + + Copy fee + + + + + Copy after fee + + + + + Copy bytes + + + + + Copy priority + + + + + Copy low output + + + + + Copy change + + + + <b>%1</b> to %2 (%3) <b>%1</b> to %2 (%3) @@ -1335,7 +1748,7 @@ Address: %4 and - + The recipient address is not valid, please recheck. The recipient address is not valid, please recheck. @@ -1369,6 +1782,21 @@ Address: %4 Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here. Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here. + + + WARNING: Invalid Bitcoin address + + + + + (no label) + (no label) + + + + WARNING: unknown change address + + SendCoinsEntry @@ -1757,9 +2185,8 @@ Address: %4 Generated coins must mature 520 blocks before they can be spent. When you generated this block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, its state will change to "not accepted" and it won't be spendable. This may occasionally happen if another node generates a block within a few seconds of yours. - - Staked coins must wait 520 blocks before they can return to balance and be spent. When you generated this proof-of-stake block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, it will change to \"not accepted\" and not be a valid stake. This may occasionally happen if another node generates a proof-of-stake block within a few seconds of yours. - Staked coins must wait 520 blocks before they can return to balance and be spent. When you generated this proof-of-stake block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, it will change to \"not accepted\" and not be a valid stake. This may occasionally happen if another node generates a proof-of-stake block within a few seconds of yours. + Staked coins must wait 520 blocks before they can return to balance and be spent. When you generated this proof-of-stake block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, it will change to \"not accepted\" and not be a valid stake. This may occasionally happen if another node generates a proof-of-stake block within a few seconds of yours. + Staked coins must wait 520 blocks before they can return to balance and be spent. When you generated this proof-of-stake block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, it will change to \"not accepted\" and not be a valid stake. This may occasionally happen if another node generates a proof-of-stake block within a few seconds of yours. @@ -1904,12 +2331,12 @@ Address: %4 Payment to yourself - + Mined Mined - + (n/a) (n/a) @@ -1994,6 +2421,7 @@ Address: %4 + Mined Mined @@ -2043,7 +2471,7 @@ Address: %4 Show transaction details - + Export Transaction Data Export Transaction Data @@ -2098,7 +2526,7 @@ Address: %4 Could not write to file %1. - + Range: Range: @@ -2111,7 +2539,7 @@ Address: %4 WalletModel - + Sending... Sending... @@ -2119,32 +2547,31 @@ Address: %4 bitcoin-core - + NovaCoin version NovaCoin version - + Usage: Usage: - Send command to -server or bitcoind - Send command to -server or bitcoind + Send command to -server or bitcoind - + List commands List commands - + Get help for a command Get help for a command - + Options: Options: @@ -2159,12 +2586,11 @@ Address: %4 Specify pid file (default: novacoind.pid) - Specify wallet file (within data directory) - Specify wallet file (within data directory) + Specify wallet file (within data directory) - + Specify data directory Specify data directory @@ -2179,7 +2605,7 @@ Address: %4 Set database disk log size in megabytes (default: 100) - + Listen for connections on <port> (default: 7777 or testnet: 17777) Listen for connections on <port> (default: 8333 or testnet: 18333) @@ -2189,32 +2615,32 @@ Address: %4 Maintain at most <n> connections to peers (default: 125) - + Connect to a node to retrieve peer addresses, and disconnect Connect to a node to retrieve peer addresses, and disconnect - + Specify your own public address Specify your own public address - + Bind to given address. Use [host]:port notation for IPv6 Bind to given address. Use [host]:port notation for IPv6 - + Threshold for disconnecting misbehaving peers (default: 100) Threshold for disconnecting misbehaving peers (default: 100) - + Number of seconds to keep misbehaving peers from reconnecting (default: 86400) Number of seconds to keep misbehaving peers from reconnecting (default: 86400) - + An error occurred while setting up the RPC port %u for listening on IPv4: %s @@ -2234,17 +2660,22 @@ Address: %4 - + + Error: Wallet unlocked for block minting only, unable to create transaction. + + + + Listen for JSON-RPC connections on <port> (default: 8344 or testnet: 18344) - + Accept command line and JSON-RPC commands Accept command line and JSON-RPC commands - + Error: Transaction creation failed @@ -2254,7 +2685,12 @@ Address: %4 - + + Find peers using DNS lookup (default: 0) + Find peers using DNS lookup (default: 1) {0)?} + + + Importing blockchain data file. @@ -2264,12 +2700,12 @@ Address: %4 - + Run in the background as a daemon and accept commands Run in the background as a daemon and accept commands - + Use the test network Use the test network @@ -2279,7 +2715,7 @@ Address: %4 Accept connections from outside (default: 1 if no -proxy or -connect) - + An error occurred while setting up the RPC port %u for listening on IPv6, falling back to IPv4: %s @@ -2289,7 +2725,7 @@ Address: %4 - + Set maximum size of high-priority/low-fee transactions in bytes (default: 27000) Set maximum size of high-priority/low-fee transactions in bytes (default: 27000) @@ -2299,9 +2735,8 @@ Address: %4 Warning: -paytxfee is set very high! This is the transaction fee you will pay if you send a transaction. - Warning: Displayed transactions may not be correct! You may need to upgrade, or other nodes may need to upgrade. - Warning: Displayed transactions may not be correct! You may need to upgrade, or other nodes may need to upgrade. + Warning: Displayed transactions may not be correct! You may need to upgrade, or other nodes may need to upgrade. @@ -2324,7 +2759,7 @@ Address: %4 Attempt to recover private keys from a corrupt wallet.dat - + Block creation options: Block creation options: @@ -2339,37 +2774,38 @@ Address: %4 Discover own IP address (default: 1 when listening and no -externalip) - + Failed to listen on any port. Use -listen=0 if you want this. Failed to listen on any port. Use -listen=0 if you want this. - Find peers using DNS lookup (default: 1) - Find peers using DNS lookup (default: 1) + Find peers using DNS lookup (default: 1) - Sync checkpoints policy (default: strict) - Sync checkpoints policy (default: strict) + Sync checkpoints policy (default: strict) - Use pooled pubkeys for the last coinstake output (default: 0) - Use pooled pubkeys for the last coinstake output (default: 0) + Use pooled pubkeys for the last coinstake output (default: 0) - Which key derivation method to use by default (default: sha512) - Which key derivation method to use by default (default: sha512) + Which key derivation method to use by default (default: sha512) - + Invalid -tor address: '%s' Invalid -tor address: '%s' - + + Invalid amount for -reservebalance=<amount> + + + + Maximum per-connection receive buffer, <n>*1000 bytes (default: 5000) Maximum per-connection receive buffer, <n>*1000 bytes (default: 5000) @@ -2379,7 +2815,7 @@ Address: %4 Maximum per-connection send buffer, <n>*1000 bytes (default: 1000) - + Only connect to nodes in network <net> (IPv4, IPv6 or Tor) Only connect to nodes in network <net> (IPv4, IPv6 or Tor) @@ -2409,7 +2845,12 @@ Address: %4 Select the version of socks proxy to use (4-5, default: 5) - + + Send command to -server or novacoind + + + + Send trace/debug info to console instead of debug.log file Send trace/debug info to console instead of debug.log file @@ -2439,7 +2880,13 @@ Address: %4 Specify connection timeout in milliseconds (default: 5000) - + + Unable to sign checkpoint, wrong checkpointkey? + + + + + Use UPnP to map the listening port (default: 0) Use UPnP to map the listening port (default: 0) @@ -2479,37 +2926,36 @@ Address: %4 - + Password for JSON-RPC connections Password for JSON-RPC connections - + Allow JSON-RPC connections from specified IP address Allow JSON-RPC connections from specified IP address - + Send commands to node running on <ip> (default: 127.0.0.1) Send commands to node running on <ip> (default: 127.0.0.1) - + Execute command when the best block changes (%s in cmd is replaced by block hash) Execute command when the best block changes (%s in cmd is replaced by block hash) - Execute command when a wallet transaction changes (%s in cmd is replaced by TxID) - Execute command when a wallet transaction changes (%s in cmd is replaced by TxID) + Execute command when a wallet transaction changes (%s in cmd is replaced by TxID) - + Upgrade wallet to latest format Upgrade wallet to latest format - + Set key pool size to <n> (default: 100) Set key pool size to <n> (default: 100) @@ -2519,7 +2965,7 @@ Address: %4 Rescan the block chain for missing wallet transactions - + How many blocks to check at startup (default: 2500, 0 = all) How many blocks to check at startup (default: 2500, 0 = all) @@ -2534,12 +2980,12 @@ Address: %4 Imports blocks from external blk000?.dat file - + Use OpenSSL (https) for JSON-RPC connections Use OpenSSL (https) for JSON-RPC connections - + Server certificate file (default: server.cert) Server certificate file (default: server.cert) @@ -2549,47 +2995,46 @@ Address: %4 Server private key (default: server.pem) - + Acceptable ciphers (default: TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:@STRENGTH) Acceptable ciphers (default: TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:@STRENGTH) - + This help message This help message - Wallet %s resides outside data directory %s. - Wallet %s resides outside data directory %s. + Wallet %s resides outside data directory %s. - + Cannot obtain a lock on data directory %s. NovaCoin is probably already running. Cannot obtain a lock on data directory %s. NovaCoin is probably already running. - + NovaCoin NovaCoin - + Unable to bind to %s on this computer (bind returned error %d, %s) Unable to bind to %s on this computer (bind returned error %d, %s) - + Connect through socks proxy Connect through socks proxy - + Allow DNS lookups for -addnode, -seednode and -connect Allow DNS lookups for -addnode, -seednode and -connect - + Loading addresses... Loading addresses... @@ -2609,22 +3054,22 @@ Address: %4 Error loading wallet.dat: Wallet requires newer version of NovaCoin - + Wallet needed to be rewritten: restart NovaCoin to complete Wallet needed to be rewritten: restart NovaCoin to complete - + Error loading wallet.dat Error loading wallet.dat - + Invalid -proxy address: '%s' Invalid -proxy address: '%s' - + Unknown network specified in -onlynet: '%s' Unknown network specified in -onlynet: '%s' @@ -2634,7 +3079,7 @@ Address: %4 Unknown -socks proxy version requested: %i - + Cannot resolve -bind address: '%s' Cannot resolve -bind address: '%s' @@ -2644,47 +3089,47 @@ Address: %4 Cannot resolve -externalip address: '%s' - + Invalid amount for -paytxfee=<amount>: '%s' Invalid amount for -paytxfee=<amount>: '%s' - + Error: could not start node Error: could not start node - + Sending... Sending... - + Invalid amount Invalid amount - + Insufficient funds Insufficient funds - + Loading block index... Loading block index... - + Add a node to connect to and attempt to keep the connection open Add a node to connect to and attempt to keep the connection open - + Unable to bind to %s on this computer. NovaCoin is probably already running. Unable to bind to %s on this computer. NovaCoin is probably already running. - + Find peers using internet relay chat (default: 1) Find peers using internet relay chat (default: 1) @@ -2699,7 +3144,7 @@ Address: %4 Loading wallet... - + Cannot downgrade wallet Cannot downgrade wallet @@ -2714,22 +3159,22 @@ Address: %4 Cannot write default address - + Rescanning... Rescanning... - + Done loading Done loading - + To use the %s option To use the %s option - + %s, you must set a rpcpassword in the configuration file: %s It is recommended you use the following random password: @@ -2748,12 +3193,12 @@ If the file does not exist, create it with owner-readable-only file permissions. - + Error Error - + You must set rpcpassword=<password> in the configuration file: %s If the file does not exist, create it with owner-readable-only file permissions. diff --git a/src/qt/locale/bitcoin_ru.ts b/src/qt/locale/bitcoin_ru.ts index 6ef6a75..c3e10e2 100644 --- a/src/qt/locale/bitcoin_ru.ts +++ b/src/qt/locale/bitcoin_ru.ts @@ -669,12 +669,281 @@ Address: %4 ClientModel - + Network Alert Сетевая Тревога + CoinControlDialog + + + Copy address + Копировать адрес + + + + Copy transaction ID + Скопировать ID транзакции + + + + Copy quantity + Копировать количество + + + + Copy fee + Копировать комиссию + + + + Copy bytes + Копировать объем + + + + Copy priority + Копировать приоритет + + + + highest + + + + + high + + + + + medium-high + + + + + medium + + + + + low-medium + + + + + low + + + + + lowest + + + + + DUST + + + + + This label turns red, if the transaction size is bigger than 10000 bytes. + + This means a fee of at least %1 per kb is required. + + Can vary +/- 1 Byte per input. + + + + + Transactions with higher priority get more likely into a block. + +This label turns red, if the priority is smaller than "medium". + + This means a fee of at least %1 per kb is required. + + + + + This label turns red, if any recipient receives an amount smaller than %1. + + This means a fee of at least %2 is required. + + Amounts below 0.546 times the minimum relay fee are shown as DUST. + + + + + This label turns red, if the change is smaller than %1. + + This means a fee of at least %2 is required. + + + + + + (no label) + [нет метки] + + + + change from %1 (%2) + + + + + (change) + + + + + Copy after fee + Копировать с комиссией + + + + Copy label + Копировать метку + + + + + Copy amount + Копировать сумму + + + + Copy low output + + + + + Copy change + Копировать сдачу + + + + (un)select all + Выбрать все + + + + Tree mode + Дерево + + + + Low Output: + Мелкие входы: + + + + yes + да + + + + + no + нет + + + + List mode + Список + + + + Coin Control + Выбор входов + + + + Quantity: + Количество: + + + + Fee: + Комиссия: + + + + Bytes: + Размер: + + + + Change: + Сдача: + + + + Priority: + Приоритет: + + + + Priority + Приоритет + + + + Label + Метка + + + + + 0 + + + + + + + + 0.00 NVC + + + + + Address + Адрес + + + + Date + Дата + + + + Confirmations + Подтверждения + + + + Confirmed + Подтверждено + + + + After Fee: + С комиссией: + + + + Amount: + Сумма: + + + + Amount + Сумма + + + EditAddressDialog @@ -745,7 +1014,7 @@ Address: %4 GUIUtil::HelpMessageBox - + NovaCoin-Qt NovaCoin-Qt @@ -944,6 +1213,16 @@ Address: %4 &Показывать адреса в списке транзакций + + Whether to show coin control features or not. + + + + + Display coin &control features (experts only!) + + + &OK О&К @@ -964,7 +1243,7 @@ Address: %4 по умолчанию - + Warning Внимание @@ -990,37 +1269,37 @@ Address: %4 - + The displayed information may be out of date. Your wallet automatically synchronizes with the NovaCoin network after a connection is established, but this process has not completed yet. Отображаемая информация может быть устаревшей. Ваш бумажник автоматически синхронизируется с сетью NovaCoin после подключения, но этот процесс пока не завершён. - + Balance: Баланс: - + Stake: Доля: - + Number of transactions: Количество транзакций: - + Unconfirmed: Не подтверждено: - + Wallet Бумажник - + Immature: Незрелые: @@ -1030,32 +1309,32 @@ Address: %4 Баланс добытых монет, который ещё не созрел - + <b>Recent transactions</b> <b>Последние транзакции</b> - + Your current balance Ваш текущий баланс - + Total of transactions that have yet to be confirmed, and do not yet count toward the current balance Общая сумма всех транзакций, которые до сих пор не подтверждены, и до сих пор не учитываются в текущем балансе - + Total of coins that was staked, and do not yet count toward the current balance Общая сумма всех монет, используемых для Proof-of-Stake, и не учитывающихся на балансе - + Total number of transactions in wallet Общее количество транзакций в Вашем бумажнике - + out of sync не синхронизировано @@ -1270,7 +1549,7 @@ Address: %4 SendCoinsDialog - + @@ -1281,7 +1560,7 @@ Address: %4 Отправка - + Send to multiple recipients at once Отправить нескольким получателям одновременно @@ -1291,7 +1570,101 @@ Address: %4 &Добавить получателя + + Coin Control Features + Выбор входов + + + Inputs... + Входы... + + + + automatically selected + автоматический выбор + + + + Insufficient funds! + Недостаточно средств! + + + + Quantity: + Количество: + + + + + 0 + + + + + Bytes: + Размер: + + + + Amount: + + + + + + + + 0.00 BTC + 123.456 BTC {0.00 ?} + + + + Priority: + Приоритет: + + + + medium + + + + + Fee: + Комиссия: + + + + Low Output: + Мелкие входы: + + + + no + нет + + + + After Fee: + С комиссией: + + + + Change + + + + + custom change address + адрес для сдачи + + + + Enter a NovaCoin address (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) + Введите NovaCoin-адрес (например 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) + + + Remove all transaction fields Удалить все поля транзакции @@ -1321,7 +1694,47 @@ Address: %4 &Отправить - + + Copy quantity + Копировать количество + + + + Copy amount + + + + + Copy fee + Копировать комиссию + + + + Copy after fee + Копировать с комиссией + + + + Copy bytes + Копировать объем + + + + Copy priority + Копировать приоритет + + + + Copy low output + + + + + Copy change + Копировать сдачу + + + <b>%1</b> to %2 (%3) <b>%1</b> адресату %2 (%3) @@ -1341,7 +1754,7 @@ Address: %4 и - + The recipient address is not valid, please recheck. Адрес получателя неверный, пожалуйста, перепроверьте. @@ -1375,6 +1788,21 @@ Address: %4 Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here. Ошибка: В транзакции отказано. Такое может произойти, если некоторые монеты уже были потрачены, например, если Вы используете одну копию файла wallet.dat, а монеты были потрачены из другой копии, но не были отмечены как потраченные в этой. + + + WARNING: Invalid Bitcoin address + + + + + (no label) + [нет метки] + + + + WARNING: unknown change address + + SendCoinsEntry @@ -1766,9 +2194,8 @@ Address: %4 Сгенерированные монеты должны подождать 520 блоков, прежде чем они могут быть потрачены. Когда Вы сгенерировали этот блок, он был отправлен в сеть для добавления в цепочку блоков. Если данная процедура не удастся, статус изменится на «не подтверждено», и монеты будут недействительны. Это иногда происходит в случае, если другой узел сгенерирует блок на несколько секунд раньше вас. - - Staked coins must wait 520 blocks before they can return to balance and be spent. When you generated this proof-of-stake block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, it will change to \"not accepted\" and not be a valid stake. This may occasionally happen if another node generates a proof-of-stake block within a few seconds of yours. - Использованные в Proof-of-Stake монеты должны подождать 520 блоков, прежде чем они вернутся на баланс и смогут быть потрачены. Когда вы сгенерировали этот proof-of-stake блок, он был отправлен в сеть для добавления в цепочку блоков. Если данная процедура не удается, статус изменится на \"не подтверждени\" и блок будет недействителен. Это иногда происходит в случае, если другой узел сгенерирует блок на несколько секунд раньше вас. + Staked coins must wait 520 blocks before they can return to balance and be spent. When you generated this proof-of-stake block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, it will change to \"not accepted\" and not be a valid stake. This may occasionally happen if another node generates a proof-of-stake block within a few seconds of yours. + Использованные в Proof-of-Stake монеты должны подождать 520 блоков, прежде чем они вернутся на баланс и смогут быть потрачены. Когда вы сгенерировали этот proof-of-stake блок, он был отправлен в сеть для добавления в цепочку блоков. Если данная процедура не удается, статус изменится на \"не подтверждени\" и блок будет недействителен. Это иногда происходит в случае, если другой узел сгенерирует блок на несколько секунд раньше вас. @@ -1915,12 +2342,12 @@ Address: %4 Отправлено себе - + Mined Добыто - + (n/a) [не доступно] @@ -2005,6 +2432,7 @@ Address: %4 + Mined Добытые @@ -2054,7 +2482,7 @@ Address: %4 Показать подробности транзакции - + Export Transaction Data Экспортировать данные транзакций @@ -2109,7 +2537,7 @@ Address: %4 Невозможно записать в файл %1. - + Range: Промежуток от: @@ -2122,7 +2550,7 @@ Address: %4 WalletModel - + Sending... Отправка.... @@ -2130,33 +2558,32 @@ Address: %4 bitcoin-core - + NovaCoin version Версия - + Usage: Использование: - Send command to -server or bitcoind - Отправить команду на -server или bitcoind + Отправить команду на -server или bitcoind - + List commands Список команд - + Get help for a command Получить помощь по команде - + Options: Опции: @@ -2171,12 +2598,11 @@ Address: %4 Указать pid-файл (по умолчанию: novacoin.pid) - Specify wallet file (within data directory) - Указать файл кошелька (в пределах DATA директории) + Указать файл кошелька (в пределах DATA директории) - + Specify data directory Укажите каталог данных @@ -2191,7 +2617,7 @@ Address: %4 Установить размер лога базы данных в мегабайтах (по умолчанию: 100) - + Listen for connections on <port> (default: 7777 or testnet: 17777) Принимать входящие подключения на <port> (по умолчанию: 7777 или 17777 в тестовой сети) @@ -2201,32 +2627,32 @@ Address: %4 Поддерживать не более <n> подключений к узлам (по умолчанию: 125) - + Connect to a node to retrieve peer addresses, and disconnect Подключиться к узлу, чтобы получить список адресов других участников и отключиться - + Specify your own public address Укажите ваш собственный публичный адрес - + Bind to given address. Use [host]:port notation for IPv6 Привязаться (bind) к указанному адресу. Используйте запись вида [хост]:порт для IPv6 - + Threshold for disconnecting misbehaving peers (default: 100) Порог для отключения неправильно ведущих себя узлов (по умолчанию: 100) - + Number of seconds to keep misbehaving peers from reconnecting (default: 86400) Число секунд блокирования неправильно ведущих себя узлов (по умолчанию: 86400) - + An error occurred while setting up the RPC port %u for listening on IPv4: %s Произошла ошибка при открытии RPC-порта %u для прослушивания на IPv4: %s @@ -2246,12 +2672,17 @@ Address: %4 Ошибка инициализации окружения БД %s! Для восстановления СДЕЛАЙТЕ РЕЗЕРВНУЮ КОПИЮ этой директории, затем удалите из нее все, кроме wallet.dat. - + + Error: Wallet unlocked for block minting only, unable to create transaction. + + + + Listen for JSON-RPC connections on <port> (default: 8344 or testnet: 18344) Прослушивать подключения JSON-RPC на <порту> (по умолчанию: 8344 или для testnet: 18344) - + Warning: error reading wallet.dat! All keys read correctly, but transaction data or address book entries might be missing or incorrect. Внимание: ошибка чтения wallet.dat! Все ключи восстановлены, но записи в адресной книге и истории транзакций могут быть некорректными. @@ -2271,7 +2702,12 @@ Address: %4 Попытка восстановления ключей из поврежденного wallet.dat - + + Find peers using DNS lookup (default: 0) + Искать узлы с помощью DNS (по умолчанию: 1) {0)?} + + + Importing blockchain data file. Импортируется файл цепи блоков. @@ -2281,12 +2717,12 @@ Address: %4 Импортируется bootstrap-файл цепи блоков. - + Run in the background as a daemon and accept commands Запускаться в фоне как демон и принимать команды - + Use the test network Использовать тестовую сеть @@ -2296,7 +2732,7 @@ Address: %4 Принимать подключения извне (по умолчанию: 1, если не используется -proxy или -connect) - + Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here. Ошибка: В транзакции отказано. Такое может произойти, если некоторые монеты уже были потрачены, например, если Вы используете одну копию файла wallet.dat, а монеты были потрачены из другой копии, но не были отмечены как потраченные в этой. @@ -2306,7 +2742,7 @@ Address: %4 Ошибка: эта транзакция требует комиссию в размере как минимум %s из-за её объёма, сложности или использования недавно полученных средств - + Set maximum size of high-priority/low-fee transactions in bytes (default: 27000) Максимальный размер высокоприоритетных/низкокомиссионных транзакций в байтах (по умолчанию: 27000) @@ -2316,9 +2752,8 @@ Address: %4 Внимание: установлено очень большое значение -paytxfee. Это комиссия, которую вы заплатите при проведении транзакции. - Warning: Displayed transactions may not be correct! You may need to upgrade, or other nodes may need to upgrade. - Внимание: отображаемые транзакции могут быть некорректны! Вам или другим узлам, возможно, следует обновиться. + Внимание: отображаемые транзакции могут быть некорректны! Вам или другим узлам, возможно, следует обновиться. @@ -2326,7 +2761,7 @@ Address: %4 Внимание: убедитесь, что дата и время на Вашем компьютере выставлены верно. Если Ваши часы идут неправильно, NovaCoin будет работать некорректно. - + Block creation options: Параметры создания блоков: @@ -2341,7 +2776,7 @@ Address: %4 Определить свой IP (по умолчанию: 1 при прослушивании и если не используется -externalip) - + Error: Transaction creation failed Ошибка: Создание транзакции не удалось @@ -2356,32 +2791,33 @@ Address: %4 Не удалось начать прослушивание на порту. Используйте -listen=0 если вас это устраивает. - Find peers using DNS lookup (default: 1) - Искать узлы с помощью DNS (по умолчанию: 1) + Искать узлы с помощью DNS (по умолчанию: 1) - Sync checkpoints policy (default: strict) - Политика синхронизированных меток (по умолчанию: strict) + Политика синхронизированных меток (по умолчанию: strict) - Use pooled pubkeys for the last coinstake output (default: 0) - Использовать для coinstake транзакций ключи из пула (по умолчанию: 0) + Использовать для coinstake транзакций ключи из пула (по умолчанию: 0) - Which key derivation method to use by default (default: sha512) - Выбор функции для создания ключа шифрования (по умолчанию: sha512) + Выбор функции для создания ключа шифрования (по умолчанию: sha512) - + Invalid -tor address: '%s' Неверный адрес -tor: '%s' - + + Invalid amount for -reservebalance=<amount> + + + + Maximum per-connection receive buffer, <n>*1000 bytes (default: 5000) Максимальный размер буфера приёма на соединение, <n>*1000 байт (по умолчанию: 5000) @@ -2391,7 +2827,7 @@ Address: %4 Максимальный размер буфера отправки на соединение, <n>*1000 байт (по умолчанию: 1000) - + Only connect to nodes in network <net> (IPv4, IPv6 or Tor) Подключаться только к узлам из сети <net> (IPv4, IPv6 или Tor) @@ -2422,7 +2858,12 @@ Address: %4 Выберите версию SOCKS-прокси (4-5, по умолчанию: 5) - + + Send command to -server or novacoind + + + + Send trace/debug info to console instead of debug.log file Выводить информацию трассировки/отладки на консоль вместо файла debug.log @@ -2452,7 +2893,13 @@ Address: %4 Таймаут соединения в миллисекундах (по умолчанию: 5000) - + + Unable to sign checkpoint, wrong checkpointkey? + + + + + Use UPnP to map the listening port (default: 0) Использовать UPnP для проброса порта (по умолчанию: 0) @@ -2492,37 +2939,36 @@ Address: %4 wallet.dat поврежден, восстановление не удалось - + Password for JSON-RPC connections Пароль для подключений JSON-RPC - + Allow JSON-RPC connections from specified IP address Разрешить подключения JSON-RPC с указанного IP - + Send commands to node running on <ip> (default: 127.0.0.1) Посылать команды узлу, запущенному на <ip> (по умолчанию: 127.0.0.1) - + Execute command when the best block changes (%s in cmd is replaced by block hash) Выполнить команду, когда появляется новый блок (%s в команде заменяется на хэш блока) - Execute command when a wallet transaction changes (%s in cmd is replaced by TxID) - Выполнить команду, когда получена новая транзакция (%s в команде заменяется на ID транзакции) + Выполнить команду, когда получена новая транзакция (%s в команде заменяется на ID транзакции) - + Upgrade wallet to latest format Обновить бумажник до последнего формата - + Set key pool size to <n> (default: 100) Установить размер запаса ключей в <n> (по умолчанию: 100) @@ -2532,7 +2978,7 @@ Address: %4 Перепроверить цепь блоков на предмет отсутствующих в бумажнике транзакций - + How many blocks to check at startup (default: 2500, 0 = all) Сколько блоков проверять при запуске (по умолчанию: 2500, 0 = все) @@ -2547,12 +2993,12 @@ Address: %4 Импортировать блоки из внешнего файла blk000?.dat - + Use OpenSSL (https) for JSON-RPC connections Использовать OpenSSL (https) для подключений JSON-RPC - + Server certificate file (default: server.cert) Файл серверного сертификата (по умолчанию: server.cert) @@ -2562,47 +3008,46 @@ Address: %4 Приватный ключ сервера (по умолчанию: server.pem) - + Acceptable ciphers (default: TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:@STRENGTH) Разрешённые алгоритмы (по умолчанию: TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:@STRENGTH) - + This help message Эта справка - Wallet %s resides outside data directory %s. - Кошелек %s находится вне рабочей директории %s. + Кошелек %s находится вне рабочей директории %s. - + Cannot obtain a lock on data directory %s. NovaCoin is probably already running. Невозможно установить блокировку на рабочую директорию %s. Возможно, бумажник уже запущен. - + NovaCoin NovaCoin - + Unable to bind to %s on this computer (bind returned error %d, %s) Невозможно привязаться к %s на этом компьютере (bind вернул ошибку %d, %s) - + Connect through socks proxy Подключаться через socks прокси - + Allow DNS lookups for -addnode, -seednode and -connect Разрешить поиск в DNS для -addnode, -seednode и -connect - + Loading addresses... Загрузка адресов... @@ -2622,22 +3067,22 @@ Address: %4 Ошибка загрузки wallet.dat: бумажник требует более новую версию NovaCoin - + Wallet needed to be rewritten: restart NovaCoin to complete Необходимо перезаписать бумажник, перезапустите NovaCoin для завершения операции. - + Error loading wallet.dat Ошибка при загрузке wallet.dat - + Invalid -proxy address: '%s' Неверный адрес -proxy: '%s' - + Unknown network specified in -onlynet: '%s' В параметре -onlynet указана неизвестная сеть: '%s' @@ -2647,7 +3092,7 @@ Address: %4 В параметре -socks запрошена неизвестная версия: %i - + Cannot resolve -bind address: '%s' Не удаётся разрешить адрес в параметре -bind: '%s' @@ -2657,47 +3102,47 @@ Address: %4 Не удаётся разрешить адрес в параметре -externalip: '%s' - + Invalid amount for -paytxfee=<amount>: '%s' Неверное количество в параметре -paytxfee=<кол-во>: '%s' - + Error: could not start node Ошибка: не удалось запустить узел - + Sending... Отправка... - + Invalid amount Неверное количество - + Insufficient funds Недостаточно монет - + Loading block index... Загрузка индекса блоков... - + Add a node to connect to and attempt to keep the connection open Добавить узел для подключения и пытаться поддерживать соединение открытым - + Unable to bind to %s on this computer. NovaCoin is probably already running. Невозможно привязаться к %s на этом компьютере. Возможно, NovaCoin уже работает. - + Find peers using internet relay chat (default: 1) Найти участников через IRC (по умолчанию: 1) @@ -2712,7 +3157,7 @@ Address: %4 Загрузка бумажника... - + Cannot downgrade wallet Не удаётся понизить версию бумажника @@ -2727,22 +3172,22 @@ Address: %4 Не удаётся записать адрес по умолчанию - + Rescanning... Сканирование... - + Done loading Загрузка завершена - + To use the %s option Чтобы использовать опцию %s - + %s, you must set a rpcpassword in the configuration file: %s It is recommended you use the following random password: @@ -2761,12 +3206,12 @@ rpcpassword=%s - + Error Ошибка - + You must set rpcpassword=<password> in the configuration file: %s If the file does not exist, create it with owner-readable-only file permissions. diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 777e046..20189a7 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -148,6 +148,7 @@ void OptionsDialog::setMapper() mapper->addMapping(ui->lang, OptionsModel::Language); mapper->addMapping(ui->unit, OptionsModel::DisplayUnit); mapper->addMapping(ui->displayAddresses, OptionsModel::DisplayAddresses); + mapper->addMapping(ui->coinControlFeatures, OptionsModel::CoinControlFeatures); } void OptionsDialog::enableApplyButton() diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 68d09cb..d1e1897 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -45,6 +45,7 @@ void OptionsModel::Init() bDisplayAddresses = settings.value("bDisplayAddresses", false).toBool(); fMinimizeToTray = settings.value("fMinimizeToTray", false).toBool(); fMinimizeOnClose = settings.value("fMinimizeOnClose", false).toBool(); + fCoinControlFeatures = settings.value("fCoinControlFeatures", false).toBool(); nTransactionFee = settings.value("nTransactionFee").toLongLong(); language = settings.value("language", "").toString(); @@ -170,6 +171,8 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const return QVariant(bitdb.GetDetach()); case Language: return settings.value("language", ""); + case CoinControlFeatures: + return QVariant(fCoinControlFeatures); default: return QVariant(); } @@ -239,6 +242,7 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in case Fee: nTransactionFee = value.toLongLong(); settings.setValue("nTransactionFee", nTransactionFee); + emit transactionFeeChanged(nTransactionFee); break; case DisplayUnit: nDisplayUnit = value.toInt(); @@ -258,6 +262,12 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in case Language: settings.setValue("language", value); break; + case CoinControlFeatures: { + fCoinControlFeatures = value.toBool(); + settings.setValue("fCoinControlFeatures", fCoinControlFeatures); + emit coinControlFeaturesChanged(fCoinControlFeatures); + } + break; default: break; } @@ -272,6 +282,11 @@ qint64 OptionsModel::getTransactionFee() return nTransactionFee; } +bool OptionsModel::getCoinControlFeatures() +{ + return fCoinControlFeatures; +} + bool OptionsModel::getMinimizeToTray() { return fMinimizeToTray; diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index 34724ad..cfd23ce 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -30,6 +30,7 @@ public: DisplayAddresses, // bool DetachDatabases, // bool Language, // QString + CoinControlFeatures, // bool OptionIDRowCount, }; @@ -48,6 +49,7 @@ public: bool getMinimizeOnClose(); int getDisplayUnit(); bool getDisplayAddresses(); + bool getCoinControlFeatures(); QString getLanguage() { return language; } private: @@ -55,10 +57,13 @@ private: bool bDisplayAddresses; bool fMinimizeToTray; bool fMinimizeOnClose; + bool fCoinControlFeatures; QString language; signals: void displayUnitChanged(int unit); + void transactionFeeChanged(qint64); + void coinControlFeaturesChanged(bool); }; #endif // OPTIONSMODEL_H diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 916cc61..35df0fd 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -1,18 +1,26 @@ #include "sendcoinsdialog.h" #include "ui_sendcoinsdialog.h" + +#include "init.h" #include "walletmodel.h" +#include "addresstablemodel.h" +#include "addressbookpage.h" + #include "bitcoinunits.h" #include "addressbookpage.h" #include "optionsmodel.h" #include "sendcoinsentry.h" #include "guiutil.h" #include "askpassphrasedialog.h" -#include "base58.h" + +#include "coincontrol.h" +#include "coincontroldialog.h" #include #include #include #include +#include SendCoinsDialog::SendCoinsDialog(QWidget *parent) : QDialog(parent), @@ -27,11 +35,48 @@ SendCoinsDialog::SendCoinsDialog(QWidget *parent) : ui->sendButton->setIcon(QIcon()); #endif +#if QT_VERSION >= 0x040700 + /* Do not move this to the XML file, Qt before 4.7 will choke on it */ + ui->lineEditCoinControlChange->setPlaceholderText(tr("Enter a NovaCoin address (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5)")); +#endif + addEntry(); connect(ui->addButton, SIGNAL(clicked()), this, SLOT(addEntry())); connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear())); + // Coin Control + ui->lineEditCoinControlChange->setFont(GUIUtil::bitcoinAddressFont()); + connect(ui->pushButtonCoinControl, SIGNAL(clicked()), this, SLOT(coinControlButtonClicked())); + connect(ui->checkBoxCoinControlChange, SIGNAL(stateChanged(int)), this, SLOT(coinControlChangeChecked(int))); + connect(ui->lineEditCoinControlChange, SIGNAL(textEdited(const QString &)), this, SLOT(coinControlChangeEdited(const QString &))); + + // Coin Control: clipboard actions + QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this); + QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this); + QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this); + QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this); + QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this); + QAction *clipboardPriorityAction = new QAction(tr("Copy priority"), this); + QAction *clipboardLowOutputAction = new QAction(tr("Copy low output"), this); + QAction *clipboardChangeAction = new QAction(tr("Copy change"), this); + connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardQuantity())); + connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAmount())); + connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardFee())); + connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAfterFee())); + connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardBytes())); + connect(clipboardPriorityAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardPriority())); + connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardLowOutput())); + connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardChange())); + ui->labelCoinControlQuantity->addAction(clipboardQuantityAction); + ui->labelCoinControlAmount->addAction(clipboardAmountAction); + ui->labelCoinControlFee->addAction(clipboardFeeAction); + ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction); + ui->labelCoinControlBytes->addAction(clipboardBytesAction); + ui->labelCoinControlPriority->addAction(clipboardPriorityAction); + ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction); + ui->labelCoinControlChange->addAction(clipboardChangeAction); + fNewRecipientAllowed = true; } @@ -52,6 +97,13 @@ void SendCoinsDialog::setModel(WalletModel *model) setBalance(model->getBalance(), model->getStake(), model->getUnconfirmedBalance(), model->getImmatureBalance()); connect(model, SIGNAL(balanceChanged(qint64, qint64, qint64, qint64)), this, SLOT(setBalance(qint64, qint64, qint64, qint64))); connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit())); + + // Coin Control + connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(coinControlUpdateLabels())); + connect(model->getOptionsModel(), SIGNAL(coinControlFeaturesChanged(bool)), this, SLOT(coinControlFeatureChanged(bool))); + connect(model->getOptionsModel(), SIGNAL(transactionFeeChanged(qint64)), this, SLOT(coinControlUpdateLabels())); + ui->frameCoinControl->setVisible(model->getOptionsModel()->getCoinControlFeatures()); + coinControlUpdateLabels(); } } @@ -117,7 +169,13 @@ void SendCoinsDialog::on_sendButton_clicked() return; } - WalletModel::SendCoinsReturn sendstatus = model->sendCoins(recipients); + WalletModel::SendCoinsReturn sendstatus; + + if (!model->getOptionsModel() || !model->getOptionsModel()->getCoinControlFeatures()) + sendstatus = model->sendCoins(recipients); + else + sendstatus = model->sendCoins(recipients, CoinControlDialog::coinControl); + switch(sendstatus.status) { case WalletModel::InvalidAddress: @@ -160,6 +218,8 @@ void SendCoinsDialog::on_sendButton_clicked() break; case WalletModel::OK: accept(); + CoinControlDialog::coinControl->UnSelectAll(); + coinControlUpdateLabels(); break; } fNewRecipientAllowed = true; @@ -195,6 +255,7 @@ SendCoinsEntry *SendCoinsDialog::addEntry() entry->setModel(model); ui->entries->addWidget(entry); connect(entry, SIGNAL(removeEntry(SendCoinsEntry*)), this, SLOT(removeEntry(SendCoinsEntry*))); + connect(entry, SIGNAL(payAmountChanged()), this, SLOT(coinControlUpdateLabels())); updateRemoveEnabled(); @@ -222,6 +283,7 @@ void SendCoinsDialog::updateRemoveEnabled() } } setupTabChain(0); + coinControlUpdateLabels(); } void SendCoinsDialog::removeEntry(SendCoinsEntry* entry) @@ -304,3 +366,155 @@ void SendCoinsDialog::updateDisplayUnit() ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->getBalance())); } } + +// Coin Control: copy label "Quantity" to clipboard +void SendCoinsDialog::coinControlClipboardQuantity() +{ + QApplication::clipboard()->setText(ui->labelCoinControlQuantity->text()); +} + +// Coin Control: copy label "Amount" to clipboard +void SendCoinsDialog::coinControlClipboardAmount() +{ + QApplication::clipboard()->setText(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" "))); +} + +// Coin Control: copy label "Fee" to clipboard +void SendCoinsDialog::coinControlClipboardFee() +{ + QApplication::clipboard()->setText(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" "))); +} + +// Coin Control: copy label "After fee" to clipboard +void SendCoinsDialog::coinControlClipboardAfterFee() +{ + QApplication::clipboard()->setText(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" "))); +} + +// Coin Control: copy label "Bytes" to clipboard +void SendCoinsDialog::coinControlClipboardBytes() +{ + QApplication::clipboard()->setText(ui->labelCoinControlBytes->text()); +} + +// Coin Control: copy label "Priority" to clipboard +void SendCoinsDialog::coinControlClipboardPriority() +{ + QApplication::clipboard()->setText(ui->labelCoinControlPriority->text()); +} + +// Coin Control: copy label "Low output" to clipboard +void SendCoinsDialog::coinControlClipboardLowOutput() +{ + QApplication::clipboard()->setText(ui->labelCoinControlLowOutput->text()); +} + +// Coin Control: copy label "Change" to clipboard +void SendCoinsDialog::coinControlClipboardChange() +{ + QApplication::clipboard()->setText(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" "))); +} + +// Coin Control: settings menu - coin control enabled/disabled by user +void SendCoinsDialog::coinControlFeatureChanged(bool checked) +{ + ui->frameCoinControl->setVisible(checked); + + if (!checked && model) // coin control features disabled + CoinControlDialog::coinControl->SetNull(); +} + +// Coin Control: button inputs -> show actual coin control dialog +void SendCoinsDialog::coinControlButtonClicked() +{ + CoinControlDialog dlg; + dlg.setModel(model); + dlg.exec(); + coinControlUpdateLabels(); +} + +// Coin Control: checkbox custom change address +void SendCoinsDialog::coinControlChangeChecked(int state) +{ + if (model) + { + if (state == Qt::Checked) + CoinControlDialog::coinControl->destChange = CBitcoinAddress(ui->lineEditCoinControlChange->text().toStdString()).Get(); + else + CoinControlDialog::coinControl->destChange = CNoDestination(); + } + + ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked)); + ui->labelCoinControlChangeLabel->setEnabled((state == Qt::Checked)); +} + +// Coin Control: custom change address changed +void SendCoinsDialog::coinControlChangeEdited(const QString & text) +{ + if (model) + { + CoinControlDialog::coinControl->destChange = CBitcoinAddress(text.toStdString()).Get(); + + // label for the change address + ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}"); + if (text.isEmpty()) + ui->labelCoinControlChangeLabel->setText(""); + else if (!CBitcoinAddress(text.toStdString()).IsValid()) + { + ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}"); + ui->labelCoinControlChangeLabel->setText(tr("WARNING: Invalid Bitcoin address")); + } + else + { + QString associatedLabel = model->getAddressTableModel()->labelForAddress(text); + if (!associatedLabel.isEmpty()) + ui->labelCoinControlChangeLabel->setText(associatedLabel); + else + { + CPubKey pubkey; + CKeyID keyid; + CBitcoinAddress(text.toStdString()).GetKeyID(keyid); + if (model->getPubKey(keyid, pubkey)) + ui->labelCoinControlChangeLabel->setText(tr("(no label)")); + else + { + ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}"); + ui->labelCoinControlChangeLabel->setText(tr("WARNING: unknown change address")); + } + } + } + } +} + +// Coin Control: update labels +void SendCoinsDialog::coinControlUpdateLabels() +{ + if (!model || !model->getOptionsModel() || !model->getOptionsModel()->getCoinControlFeatures()) + return; + + // set pay amounts + CoinControlDialog::payAmounts.clear(); + for(int i = 0; i < ui->entries->count(); ++i) + { + SendCoinsEntry *entry = qobject_cast(ui->entries->itemAt(i)->widget()); + if(entry) + CoinControlDialog::payAmounts.append(entry->getValue().amount); + } + + if (CoinControlDialog::coinControl->HasSelected()) + { + // actual coin control calculation + CoinControlDialog::updateLabels(model, this); + + // show coin control stats + ui->labelCoinControlAutomaticallySelected->hide(); + ui->widgetCoinControl->show(); + } + else + { + // hide coin control stats + ui->labelCoinControlAutomaticallySelected->show(); + ui->widgetCoinControl->hide(); + ui->labelCoinControlInsuffFunds->hide(); + } +} diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h index 32bb61a..68424bb 100644 --- a/src/qt/sendcoinsdialog.h +++ b/src/qt/sendcoinsdialog.h @@ -2,6 +2,7 @@ #define SENDCOINSDIALOG_H #include +#include namespace Ui { class SendCoinsDialog; @@ -49,6 +50,19 @@ private slots: void on_sendButton_clicked(); void removeEntry(SendCoinsEntry* entry); void updateDisplayUnit(); + void coinControlFeatureChanged(bool); + void coinControlButtonClicked(); + void coinControlChangeChecked(int); + void coinControlChangeEdited(const QString &); + void coinControlUpdateLabels(); + void coinControlClipboardQuantity(); + void coinControlClipboardAmount(); + void coinControlClipboardFee(); + void coinControlClipboardAfterFee(); + void coinControlClipboardBytes(); + void coinControlClipboardPriority(); + void coinControlClipboardLowOutput(); + void coinControlClipboardChange(); }; #endif // SENDCOINSDIALOG_H diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index cd18619..0b5db26 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -72,6 +72,8 @@ void SendCoinsEntry::setModel(WalletModel *model) if(model && model->getOptionsModel()) connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit())); + connect(ui->payAmount, SIGNAL(textChanged()), this, SIGNAL(payAmountChanged())); + clear(); } diff --git a/src/qt/sendcoinsentry.h b/src/qt/sendcoinsentry.h index 0ac14c1..3271e0d 100644 --- a/src/qt/sendcoinsentry.h +++ b/src/qt/sendcoinsentry.h @@ -39,6 +39,7 @@ public slots: signals: void removeEntry(SendCoinsEntry *entry); + void payAmountChanged(); private slots: void on_deleteButton_clicked(); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 2dd321b..df006a3 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -129,7 +129,7 @@ bool WalletModel::validateAddress(const QString &address) return addressParsed.IsValid(); } -WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList &recipients) +WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList &recipients, const CCoinControl *coinControl) { qint64 total = 0; QSet setAddress; @@ -161,12 +161,19 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList getBalance()) + int64 nBalance = 0; + std::vector vCoins; + wallet->AvailableCoins(vCoins, true, coinControl); + + BOOST_FOREACH(const COutput& out, vCoins) + nBalance += out.tx->vout[out.i].nValue; + + if(total > nBalance) { return AmountExceedsBalance; } - if((total + nTransactionFee) > getBalance()) + if((total + nTransactionFee) > nBalance) { return SendCoinsReturn(AmountWithFeeExceedsBalance, nTransactionFee); } @@ -186,11 +193,11 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QListCreateTransaction(vecSend, wtx, keyChange, nFeeRequired); + bool fCreated = wallet->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, coinControl); if(!fCreated) { - if((total + nFeeRequired) > wallet->GetBalance()) + if((total + nFeeRequired) > nBalance) // FIXME: could cause collisions in the future { return SendCoinsReturn(AmountWithFeeExceedsBalance, nFeeRequired); } @@ -381,3 +388,70 @@ void WalletModel::UnlockContext::CopyFrom(const UnlockContext& rhs) *this = rhs; rhs.relock = false; } + +bool WalletModel::getPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const +{ + return wallet->GetPubKey(address, vchPubKeyOut); +} + +// returns a list of COutputs from COutPoints +void WalletModel::getOutputs(const std::vector& vOutpoints, std::vector& vOutputs) +{ + BOOST_FOREACH(const COutPoint& outpoint, vOutpoints) + { + if (!wallet->mapWallet.count(outpoint.hash)) continue; + COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, wallet->mapWallet[outpoint.hash].GetDepthInMainChain()); + vOutputs.push_back(out); + } +} + +// AvailableCoins + LockedCoins grouped by wallet address (put change in one group with wallet address) +void WalletModel::listCoins(std::map >& mapCoins) const +{ + std::vector vCoins; + wallet->AvailableCoins(vCoins); + std::vector vLockedCoins; + + // add locked coins + BOOST_FOREACH(const COutPoint& outpoint, vLockedCoins) + { + if (!wallet->mapWallet.count(outpoint.hash)) continue; + COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, wallet->mapWallet[outpoint.hash].GetDepthInMainChain()); + vCoins.push_back(out); + } + + BOOST_FOREACH(const COutput& out, vCoins) + { + COutput cout = out; + + while (wallet->IsChange(cout.tx->vout[cout.i]) && cout.tx->vin.size() > 0 && wallet->IsMine(cout.tx->vin[0])) + { + if (!wallet->mapWallet.count(cout.tx->vin[0].prevout.hash)) break; + cout = COutput(&wallet->mapWallet[cout.tx->vin[0].prevout.hash], cout.tx->vin[0].prevout.n, 0); + } + + CTxDestination address; + if(!ExtractDestination(cout.tx->vout[cout.i].scriptPubKey, address)) continue; + mapCoins[CBitcoinAddress(address).ToString().c_str()].push_back(out); + } +} + +bool WalletModel::isLockedCoin(uint256 hash, unsigned int n) const +{ + return false; +} + +void WalletModel::lockCoin(COutPoint& output) +{ + return; +} + +void WalletModel::unlockCoin(COutPoint& output) +{ + return; +} + +void WalletModel::listLockedCoins(std::vector& vOutpts) +{ + return; +} diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index b024edb..b33ff75 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -2,6 +2,8 @@ #define WALLETMODEL_H #include +#include +#include #include "allocators.h" /* for SecureString */ @@ -9,6 +11,12 @@ class OptionsModel; class AddressTableModel; class TransactionTableModel; class CWallet; +class CKeyID; +class CPubKey; +class COutput; +class COutPoint; +class uint256; +class CCoinControl; QT_BEGIN_NAMESPACE class QTimer; @@ -68,7 +76,7 @@ public: // Return status record for SendCoins, contains error id + information struct SendCoinsReturn { - SendCoinsReturn(StatusCode status, + SendCoinsReturn(StatusCode status=Aborted, qint64 fee=0, QString hex=QString()): status(status), fee(fee), hex(hex) {} @@ -78,7 +86,7 @@ public: }; // Send coins to a list of recipients - SendCoinsReturn sendCoins(const QList &recipients); + SendCoinsReturn sendCoins(const QList &recipients, const CCoinControl *coinControl=NULL); // Wallet encryption bool setWalletEncrypted(bool encrypted, const SecureString &passphrase); @@ -110,6 +118,14 @@ public: UnlockContext requestUnlock(); + bool getPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const; + void getOutputs(const std::vector& vOutpoints, std::vector& vOutputs); + void listCoins(std::map >& mapCoins) const; + bool isLockedCoin(uint256 hash, unsigned int n) const; + void lockCoin(COutPoint& output); + void unlockCoin(COutPoint& output); + void listLockedCoins(std::vector& vOutpts); + private: CWallet *wallet; diff --git a/src/wallet.cpp b/src/wallet.cpp index 3036f23..67a50ab 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -10,6 +10,7 @@ #include "ui_interface.h" #include "base58.h" #include "kernel.h" +#include "coincontrol.h" #include using namespace std; @@ -1026,7 +1027,7 @@ int64 CWallet::GetImmatureBalance() const } // populate vCoins with vector of spendable COutputs -void CWallet::AvailableCoins(vector& vCoins, bool fOnlyConfirmed) const +void CWallet::AvailableCoins(vector& vCoins, bool fOnlyConfirmed, const CCoinControl *coinControl) const { vCoins.clear(); @@ -1049,8 +1050,10 @@ void CWallet::AvailableCoins(vector& vCoins, bool fOnlyConfirmed) const continue; for (unsigned int i = 0; i < pcoin->vout.size(); i++) - if (!(pcoin->IsSpent(i)) && IsMine(pcoin->vout[i]) && pcoin->vout[i].nValue > 0) + if (!(pcoin->IsSpent(i)) && IsMine(pcoin->vout[i]) && pcoin->vout[i].nValue > 0 && + (!coinControl || !coinControl->HasSelected() || coinControl->IsSelected((*it).first, i))) vCoins.push_back(COutput(pcoin, i, pcoin->GetDepthInMainChain())); + } } } @@ -1225,20 +1228,28 @@ bool CWallet::SelectCoinsMinConf(int64 nTargetValue, unsigned int nSpendTime, in return true; } -bool CWallet::SelectCoins(int64 nTargetValue, unsigned int nSpendTime, set >& setCoinsRet, int64& nValueRet) const +bool CWallet::SelectCoins(int64 nTargetValue, unsigned int nSpendTime, set >& setCoinsRet, int64& nValueRet, const CCoinControl* coinControl) const { vector vCoins; - AvailableCoins(vCoins); + AvailableCoins(vCoins, true, coinControl); + + // coin control -> return all selected outputs (we want all selected to go into the transaction for sure) + if (coinControl && coinControl->HasSelected()) + { + BOOST_FOREACH(const COutput& out, vCoins) + { + nValueRet += out.tx->vout[out.i].nValue; + setCoinsRet.insert(make_pair(out.tx, out.i)); + } + return (nValueRet >= nTargetValue); + } return (SelectCoinsMinConf(nTargetValue, nSpendTime, 1, 6, vCoins, setCoinsRet, nValueRet) || SelectCoinsMinConf(nTargetValue, nSpendTime, 1, 1, vCoins, setCoinsRet, nValueRet) || SelectCoinsMinConf(nTargetValue, nSpendTime, 0, 1, vCoins, setCoinsRet, nValueRet)); } - - - -bool CWallet::CreateTransaction(const vector >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet) +bool CWallet::CreateTransaction(const vector >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet, const CCoinControl* coinControl) { int64 nValue = 0; BOOST_FOREACH (const PAIRTYPE(CScript, int64)& s, vecSend) @@ -1273,7 +1284,7 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CW // Choose coins to use set > setCoins; int64 nValueIn = 0; - if (!SelectCoins(nTotalValue, wtxNew.nTime, setCoins, nValueIn)) + if (!SelectCoins(nTotalValue, wtxNew.nTime, setCoins, nValueIn, coinControl)) return false; BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins) { @@ -1301,22 +1312,30 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CW if (nChange > 0) { - // Note: We use a new key here to keep it from being obvious which side is the change. - // The drawback is that by not reusing a previous key, the change may be lost if a - // backup is restored, if the backup doesn't have the new private key for the change. - // If we reused the old key, it would be possible to add code to look for and - // rediscover unknown transactions that were written with keys of ours to recover - // post-backup change. - - // Reserve a new key pair from key pool - CPubKey vchPubKey = reservekey.GetReservedKey(); - // assert(mapKeys.count(vchPubKey)); - // Fill a vout to ourself // TODO: pass in scriptChange instead of reservekey so // change transaction isn't always pay-to-bitcoin-address CScript scriptChange; - scriptChange.SetDestination(vchPubKey.GetID()); + + // coin control: send change to custom address + if (coinControl && !boost::get(&coinControl->destChange)) + scriptChange.SetDestination(coinControl->destChange); + + // no coin control: send change to newly generated address + else + { + // Note: We use a new key here to keep it from being obvious which side is the change. + // The drawback is that by not reusing a previous key, the change may be lost if a + // backup is restored, if the backup doesn't have the new private key for the change. + // If we reused the old key, it would be possible to add code to look for and + // rediscover unknown transactions that were written with keys of ours to recover + // post-backup change. + + // Reserve a new key pair from key pool + CPubKey vchPubKey = reservekey.GetReservedKey(); + + scriptChange.SetDestination(vchPubKey.GetID()); + } // Insert change txn at random position: vector::iterator position = wtxNew.vout.begin()+GetRandInt(wtxNew.vout.size()); @@ -1343,7 +1362,7 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CW // Check that enough fee is included int64 nPayFee = nTransactionFee * (1 + (int64)nBytes / 1000); - int64 nMinFee = wtxNew.GetMinFee(1, false, GMF_SEND); + int64 nMinFee = wtxNew.GetMinFee(1, false, GMF_SEND, nBytes); if (nFeeRet < max(nPayFee, nMinFee)) { @@ -1362,11 +1381,11 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CW return true; } -bool CWallet::CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet) +bool CWallet::CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet, const CCoinControl* coinControl) { vector< pair > vecSend; vecSend.push_back(make_pair(scriptPubKey, nValue)); - return CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet); + return CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet, coinControl); } // NovaCoin: get current stake weight diff --git a/src/wallet.h b/src/wallet.h index d6cf174..f52dc59 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -23,6 +23,7 @@ class CAccountingEntry; class CWalletTx; class CReserveKey; class COutput; +class CCoinControl; /** (client) version numbers for particular wallet features */ enum WalletFeature @@ -76,7 +77,7 @@ public: class CWallet : public CCryptoKeyStore { private: - bool SelectCoins(int64 nTargetValue, unsigned int nSpendTime, std::set >& setCoinsRet, int64& nValueRet) const; + bool SelectCoins(int64 nTargetValue, unsigned int nSpendTime, std::set >& setCoinsRet, int64& nValueRet, const CCoinControl *coinControl=NULL) const; CWalletDB *pwalletdbEncryption; @@ -132,7 +133,7 @@ public: // check whether we are allowed to upgrade (or already support) to the named feature bool CanSupportFeature(enum WalletFeature wf) { return nWalletMaxVersion >= wf; } - void AvailableCoins(std::vector& vCoins, bool fOnlyConfirmed=true) const; + void AvailableCoins(std::vector& vCoins, bool fOnlyConfirmed=true, const CCoinControl *coinControl=NULL) const; bool SelectCoinsMinConf(int64 nTargetValue, unsigned int nSpendTime, int nConfMine, int nConfTheirs, std::vector vCoins, std::set >& setCoinsRet, int64& nValueRet) const; // keystore implementation // Generate a new key @@ -188,8 +189,8 @@ public: int64 GetImmatureBalance() const; int64 GetStake() const; int64 GetNewMint() const; - bool CreateTransaction(const std::vector >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet); - bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet); + bool CreateTransaction(const std::vector >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet, const CCoinControl *coinControl=NULL); + bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet, const CCoinControl *coinControl=NULL); bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey); uint64 GetStakeWeight(const CKeyStore& keystore, enum StakeWeightMode mode=STAKE_NORMAL); -- 1.7.1