Merge #10769: [Qt] replace fee slider with a Dropdown, extend conf. targets
[bitcoinplatinum.git] / src / qt / sendcoinsdialog.cpp
blob86401d3bb4b5142793a1de6f2930db3668b2c2a1
1 // Copyright (c) 2011-2016 The Bitcoin Core developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 #include "sendcoinsdialog.h"
6 #include "ui_sendcoinsdialog.h"
8 #include "addresstablemodel.h"
9 #include "bitcoinunits.h"
10 #include "clientmodel.h"
11 #include "coincontroldialog.h"
12 #include "guiutil.h"
13 #include "optionsmodel.h"
14 #include "platformstyle.h"
15 #include "sendcoinsentry.h"
16 #include "walletmodel.h"
18 #include "base58.h"
19 #include "chainparams.h"
20 #include "wallet/coincontrol.h"
21 #include "validation.h" // mempool and minRelayTxFee
22 #include "ui_interface.h"
23 #include "txmempool.h"
24 #include "policy/fees.h"
25 #include "wallet/wallet.h"
27 #include <QFontMetrics>
28 #include <QMessageBox>
29 #include <QScrollBar>
30 #include <QSettings>
31 #include <QTextDocument>
32 #include <QTimer>
34 static const std::array<int, 9> confTargets = { {2, 4, 6, 12, 24, 48, 144, 504, 1008} };
35 int getConfTargetForIndex(int index) {
36 if (index+1 > static_cast<int>(confTargets.size())) {
37 return confTargets.back();
39 if (index < 0) {
40 return confTargets[0];
42 return confTargets[index];
44 int getIndexForConfTarget(int target) {
45 for (unsigned int i = 0; i < confTargets.size(); i++) {
46 if (confTargets[i] >= target) {
47 return i;
50 return confTargets.size() - 1;
53 SendCoinsDialog::SendCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) :
54 QDialog(parent),
55 ui(new Ui::SendCoinsDialog),
56 clientModel(0),
57 model(0),
58 fNewRecipientAllowed(true),
59 fFeeMinimized(true),
60 platformStyle(_platformStyle)
62 ui->setupUi(this);
64 if (!_platformStyle->getImagesOnButtons()) {
65 ui->addButton->setIcon(QIcon());
66 ui->clearButton->setIcon(QIcon());
67 ui->sendButton->setIcon(QIcon());
68 } else {
69 ui->addButton->setIcon(_platformStyle->SingleColorIcon(":/icons/add"));
70 ui->clearButton->setIcon(_platformStyle->SingleColorIcon(":/icons/remove"));
71 ui->sendButton->setIcon(_platformStyle->SingleColorIcon(":/icons/send"));
74 GUIUtil::setupAddressWidget(ui->lineEditCoinControlChange, this);
76 addEntry();
78 connect(ui->addButton, SIGNAL(clicked()), this, SLOT(addEntry()));
79 connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear()));
81 // Coin Control
82 connect(ui->pushButtonCoinControl, SIGNAL(clicked()), this, SLOT(coinControlButtonClicked()));
83 connect(ui->checkBoxCoinControlChange, SIGNAL(stateChanged(int)), this, SLOT(coinControlChangeChecked(int)));
84 connect(ui->lineEditCoinControlChange, SIGNAL(textEdited(const QString &)), this, SLOT(coinControlChangeEdited(const QString &)));
86 // Coin Control: clipboard actions
87 QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this);
88 QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this);
89 QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this);
90 QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this);
91 QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this);
92 QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this);
93 QAction *clipboardChangeAction = new QAction(tr("Copy change"), this);
94 connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardQuantity()));
95 connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAmount()));
96 connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardFee()));
97 connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAfterFee()));
98 connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardBytes()));
99 connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardLowOutput()));
100 connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardChange()));
101 ui->labelCoinControlQuantity->addAction(clipboardQuantityAction);
102 ui->labelCoinControlAmount->addAction(clipboardAmountAction);
103 ui->labelCoinControlFee->addAction(clipboardFeeAction);
104 ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction);
105 ui->labelCoinControlBytes->addAction(clipboardBytesAction);
106 ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction);
107 ui->labelCoinControlChange->addAction(clipboardChangeAction);
109 // init transaction fee section
110 QSettings settings;
111 if (!settings.contains("fFeeSectionMinimized"))
112 settings.setValue("fFeeSectionMinimized", true);
113 if (!settings.contains("nFeeRadio") && settings.contains("nTransactionFee") && settings.value("nTransactionFee").toLongLong() > 0) // compatibility
114 settings.setValue("nFeeRadio", 1); // custom
115 if (!settings.contains("nFeeRadio"))
116 settings.setValue("nFeeRadio", 0); // recommended
117 if (!settings.contains("nCustomFeeRadio") && settings.contains("nTransactionFee") && settings.value("nTransactionFee").toLongLong() > 0) // compatibility
118 settings.setValue("nCustomFeeRadio", 1); // total at least
119 if (!settings.contains("nCustomFeeRadio"))
120 settings.setValue("nCustomFeeRadio", 0); // per kilobyte
121 if (!settings.contains("nSmartFeeSliderPosition"))
122 settings.setValue("nSmartFeeSliderPosition", 0);
123 if (!settings.contains("nTransactionFee"))
124 settings.setValue("nTransactionFee", (qint64)DEFAULT_TRANSACTION_FEE);
125 if (!settings.contains("fPayOnlyMinFee"))
126 settings.setValue("fPayOnlyMinFee", false);
127 ui->groupFee->setId(ui->radioSmartFee, 0);
128 ui->groupFee->setId(ui->radioCustomFee, 1);
129 ui->groupFee->button((int)std::max(0, std::min(1, settings.value("nFeeRadio").toInt())))->setChecked(true);
130 ui->groupCustomFee->setId(ui->radioCustomPerKilobyte, 0);
131 ui->groupCustomFee->button((int)std::max(0, std::min(1, settings.value("nCustomFeeRadio").toInt())))->setChecked(true);
132 ui->customFee->setValue(settings.value("nTransactionFee").toLongLong());
133 ui->checkBoxMinimumFee->setChecked(settings.value("fPayOnlyMinFee").toBool());
134 minimizeFeeSection(settings.value("fFeeSectionMinimized").toBool());
137 void SendCoinsDialog::setClientModel(ClientModel *_clientModel)
139 this->clientModel = _clientModel;
141 if (_clientModel) {
142 connect(_clientModel, SIGNAL(numBlocksChanged(int,QDateTime,double,bool)), this, SLOT(updateSmartFeeLabel()));
146 void SendCoinsDialog::setModel(WalletModel *_model)
148 this->model = _model;
150 if(_model && _model->getOptionsModel())
152 for(int i = 0; i < ui->entries->count(); ++i)
154 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
155 if(entry)
157 entry->setModel(_model);
161 setBalance(_model->getBalance(), _model->getUnconfirmedBalance(), _model->getImmatureBalance(),
162 _model->getWatchBalance(), _model->getWatchUnconfirmedBalance(), _model->getWatchImmatureBalance());
163 connect(_model, SIGNAL(balanceChanged(CAmount,CAmount,CAmount,CAmount,CAmount,CAmount)), this, SLOT(setBalance(CAmount,CAmount,CAmount,CAmount,CAmount,CAmount)));
164 connect(_model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
165 updateDisplayUnit();
167 // Coin Control
168 connect(_model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(coinControlUpdateLabels()));
169 connect(_model->getOptionsModel(), SIGNAL(coinControlFeaturesChanged(bool)), this, SLOT(coinControlFeatureChanged(bool)));
170 ui->frameCoinControl->setVisible(_model->getOptionsModel()->getCoinControlFeatures());
171 coinControlUpdateLabels();
173 // fee section
174 for (const int &n : confTargets) {
175 ui->confTargetSelector->addItem(tr("%1 (%2 blocks)").arg(GUIUtil::formatNiceTimeOffset(n*Params().GetConsensus().nPowTargetSpacing)).arg(n));
177 connect(ui->confTargetSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(updateSmartFeeLabel()));
178 connect(ui->confTargetSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(updateGlobalFeeVariables()));
179 connect(ui->confTargetSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(coinControlUpdateLabels()));
180 connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(updateFeeSectionControls()));
181 connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(updateGlobalFeeVariables()));
182 connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels()));
183 connect(ui->groupCustomFee, SIGNAL(buttonClicked(int)), this, SLOT(updateGlobalFeeVariables()));
184 connect(ui->groupCustomFee, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels()));
185 connect(ui->customFee, SIGNAL(valueChanged()), this, SLOT(updateGlobalFeeVariables()));
186 connect(ui->customFee, SIGNAL(valueChanged()), this, SLOT(coinControlUpdateLabels()));
187 connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(setMinimumFee()));
188 connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateFeeSectionControls()));
189 connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateGlobalFeeVariables()));
190 connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels()));
191 connect(ui->optInRBF, SIGNAL(stateChanged(int)), this, SLOT(updateSmartFeeLabel()));
192 connect(ui->optInRBF, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels()));
193 ui->customFee->setSingleStep(CWallet::GetRequiredFee(1000));
194 updateFeeSectionControls();
195 updateMinFeeLabel();
196 updateSmartFeeLabel();
197 updateGlobalFeeVariables();
199 // set default rbf checkbox state
200 ui->optInRBF->setCheckState(model->getDefaultWalletRbf() ? Qt::Checked : Qt::Unchecked);
202 // set the smartfee-sliders default value (wallets default conf.target or last stored value)
203 QSettings settings;
204 if (settings.value("nSmartFeeSliderPosition").toInt() != 0) {
205 // migrate nSmartFeeSliderPosition to nConfTarget
206 // nConfTarget is available since 0.15 (replaced nSmartFeeSliderPosition)
207 int nConfirmTarget = 25 - settings.value("nSmartFeeSliderPosition").toInt(); // 25 == old slider range
208 settings.setValue("nConfTarget", nConfirmTarget);
209 settings.remove("nSmartFeeSliderPosition");
211 if (settings.value("nConfTarget").toInt() == 0)
212 ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(model->getDefaultConfirmTarget()));
213 else
214 ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(settings.value("nConfTarget").toInt()));
218 SendCoinsDialog::~SendCoinsDialog()
220 QSettings settings;
221 settings.setValue("fFeeSectionMinimized", fFeeMinimized);
222 settings.setValue("nFeeRadio", ui->groupFee->checkedId());
223 settings.setValue("nCustomFeeRadio", ui->groupCustomFee->checkedId());
224 settings.setValue("nConfTarget", getConfTargetForIndex(ui->confTargetSelector->currentIndex()));
225 settings.setValue("nTransactionFee", (qint64)ui->customFee->value());
226 settings.setValue("fPayOnlyMinFee", ui->checkBoxMinimumFee->isChecked());
228 delete ui;
231 void SendCoinsDialog::on_sendButton_clicked()
233 if(!model || !model->getOptionsModel())
234 return;
236 QList<SendCoinsRecipient> recipients;
237 bool valid = true;
239 for(int i = 0; i < ui->entries->count(); ++i)
241 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
242 if(entry)
244 if(entry->validate())
246 recipients.append(entry->getValue());
248 else
250 valid = false;
255 if(!valid || recipients.isEmpty())
257 return;
260 fNewRecipientAllowed = false;
261 WalletModel::UnlockContext ctx(model->requestUnlock());
262 if(!ctx.isValid())
264 // Unlock wallet was cancelled
265 fNewRecipientAllowed = true;
266 return;
269 // prepare transaction for getting txFee earlier
270 WalletModelTransaction currentTransaction(recipients);
271 WalletModel::SendCoinsReturn prepareStatus;
273 // Always use a CCoinControl instance, use the CoinControlDialog instance if CoinControl has been enabled
274 CCoinControl ctrl;
275 if (model->getOptionsModel()->getCoinControlFeatures())
276 ctrl = *CoinControlDialog::coinControl;
277 if (ui->radioSmartFee->isChecked())
278 ctrl.nConfirmTarget = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
279 else
280 ctrl.nConfirmTarget = 0;
282 ctrl.signalRbf = ui->optInRBF->isChecked();
284 prepareStatus = model->prepareTransaction(currentTransaction, &ctrl);
286 // process prepareStatus and on error generate message shown to user
287 processSendCoinsReturn(prepareStatus,
288 BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTransactionFee()));
290 if(prepareStatus.status != WalletModel::OK) {
291 fNewRecipientAllowed = true;
292 return;
295 CAmount txFee = currentTransaction.getTransactionFee();
297 // Format confirmation message
298 QStringList formatted;
299 for (const SendCoinsRecipient &rcp : currentTransaction.getRecipients())
301 // generate bold amount string
302 QString amount = "<b>" + BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
303 amount.append("</b>");
304 // generate monospace address string
305 QString address = "<span style='font-family: monospace;'>" + rcp.address;
306 address.append("</span>");
308 QString recipientElement;
310 if (!rcp.paymentRequest.IsInitialized()) // normal payment
312 if(rcp.label.length() > 0) // label with address
314 recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.label));
315 recipientElement.append(QString(" (%1)").arg(address));
317 else // just address
319 recipientElement = tr("%1 to %2").arg(amount, address);
322 else if(!rcp.authenticatedMerchant.isEmpty()) // authenticated payment request
324 recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.authenticatedMerchant));
326 else // unauthenticated payment request
328 recipientElement = tr("%1 to %2").arg(amount, address);
331 formatted.append(recipientElement);
334 QString questionString = tr("Are you sure you want to send?");
335 questionString.append("<br /><br />%1");
337 if(txFee > 0)
339 // append fee string if a fee is required
340 questionString.append("<hr /><span style='color:#aa0000;'>");
341 questionString.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee));
342 questionString.append("</span> ");
343 questionString.append(tr("added as transaction fee"));
345 // append transaction size
346 questionString.append(" (" + QString::number((double)currentTransaction.getTransactionSize() / 1000) + " kB)");
349 // add total amount in all subdivision units
350 questionString.append("<hr />");
351 CAmount totalAmount = currentTransaction.getTotalTransactionAmount() + txFee;
352 QStringList alternativeUnits;
353 for (BitcoinUnits::Unit u : BitcoinUnits::availableUnits())
355 if(u != model->getOptionsModel()->getDisplayUnit())
356 alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
358 questionString.append(tr("Total Amount %1")
359 .arg(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), totalAmount)));
360 questionString.append(QString("<span style='font-size:10pt;font-weight:normal;'><br />(=%2)</span>")
361 .arg(alternativeUnits.join(" " + tr("or") + "<br />")));
363 if (ui->optInRBF->isChecked())
365 questionString.append("<hr /><span>");
366 questionString.append(tr("This transaction signals replaceability (optin-RBF)."));
367 questionString.append("</span>");
370 SendConfirmationDialog confirmationDialog(tr("Confirm send coins"),
371 questionString.arg(formatted.join("<br />")), SEND_CONFIRM_DELAY, this);
372 confirmationDialog.exec();
373 QMessageBox::StandardButton retval = (QMessageBox::StandardButton)confirmationDialog.result();
375 if(retval != QMessageBox::Yes)
377 fNewRecipientAllowed = true;
378 return;
381 // now send the prepared transaction
382 WalletModel::SendCoinsReturn sendStatus = model->sendCoins(currentTransaction);
383 // process sendStatus and on error generate message shown to user
384 processSendCoinsReturn(sendStatus);
386 if (sendStatus.status == WalletModel::OK)
388 accept();
389 CoinControlDialog::coinControl->UnSelectAll();
390 coinControlUpdateLabels();
392 fNewRecipientAllowed = true;
395 void SendCoinsDialog::clear()
397 // Remove entries until only one left
398 while(ui->entries->count())
400 ui->entries->takeAt(0)->widget()->deleteLater();
402 addEntry();
404 updateTabsAndLabels();
407 void SendCoinsDialog::reject()
409 clear();
412 void SendCoinsDialog::accept()
414 clear();
417 SendCoinsEntry *SendCoinsDialog::addEntry()
419 SendCoinsEntry *entry = new SendCoinsEntry(platformStyle, this);
420 entry->setModel(model);
421 ui->entries->addWidget(entry);
422 connect(entry, SIGNAL(removeEntry(SendCoinsEntry*)), this, SLOT(removeEntry(SendCoinsEntry*)));
423 connect(entry, SIGNAL(payAmountChanged()), this, SLOT(coinControlUpdateLabels()));
424 connect(entry, SIGNAL(subtractFeeFromAmountChanged()), this, SLOT(coinControlUpdateLabels()));
426 // Focus the field, so that entry can start immediately
427 entry->clear();
428 entry->setFocus();
429 ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
430 qApp->processEvents();
431 QScrollBar* bar = ui->scrollArea->verticalScrollBar();
432 if(bar)
433 bar->setSliderPosition(bar->maximum());
435 updateTabsAndLabels();
436 return entry;
439 void SendCoinsDialog::updateTabsAndLabels()
441 setupTabChain(0);
442 coinControlUpdateLabels();
445 void SendCoinsDialog::removeEntry(SendCoinsEntry* entry)
447 entry->hide();
449 // If the last entry is about to be removed add an empty one
450 if (ui->entries->count() == 1)
451 addEntry();
453 entry->deleteLater();
455 updateTabsAndLabels();
458 QWidget *SendCoinsDialog::setupTabChain(QWidget *prev)
460 for(int i = 0; i < ui->entries->count(); ++i)
462 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
463 if(entry)
465 prev = entry->setupTabChain(prev);
468 QWidget::setTabOrder(prev, ui->sendButton);
469 QWidget::setTabOrder(ui->sendButton, ui->clearButton);
470 QWidget::setTabOrder(ui->clearButton, ui->addButton);
471 return ui->addButton;
474 void SendCoinsDialog::setAddress(const QString &address)
476 SendCoinsEntry *entry = 0;
477 // Replace the first entry if it is still unused
478 if(ui->entries->count() == 1)
480 SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
481 if(first->isClear())
483 entry = first;
486 if(!entry)
488 entry = addEntry();
491 entry->setAddress(address);
494 void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv)
496 if(!fNewRecipientAllowed)
497 return;
499 SendCoinsEntry *entry = 0;
500 // Replace the first entry if it is still unused
501 if(ui->entries->count() == 1)
503 SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
504 if(first->isClear())
506 entry = first;
509 if(!entry)
511 entry = addEntry();
514 entry->setValue(rv);
515 updateTabsAndLabels();
518 bool SendCoinsDialog::handlePaymentRequest(const SendCoinsRecipient &rv)
520 // Just paste the entry, all pre-checks
521 // are done in paymentserver.cpp.
522 pasteEntry(rv);
523 return true;
526 void SendCoinsDialog::setBalance(const CAmount& balance, const CAmount& unconfirmedBalance, const CAmount& immatureBalance,
527 const CAmount& watchBalance, const CAmount& watchUnconfirmedBalance, const CAmount& watchImmatureBalance)
529 Q_UNUSED(unconfirmedBalance);
530 Q_UNUSED(immatureBalance);
531 Q_UNUSED(watchBalance);
532 Q_UNUSED(watchUnconfirmedBalance);
533 Q_UNUSED(watchImmatureBalance);
535 if(model && model->getOptionsModel())
537 ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), balance));
541 void SendCoinsDialog::updateDisplayUnit()
543 setBalance(model->getBalance(), 0, 0, 0, 0, 0);
544 ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
545 updateMinFeeLabel();
546 updateSmartFeeLabel();
549 void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg)
551 QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
552 // Default to a warning message, override if error message is needed
553 msgParams.second = CClientUIInterface::MSG_WARNING;
555 // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn.
556 // WalletModel::TransactionCommitFailed is used only in WalletModel::sendCoins()
557 // all others are used only in WalletModel::prepareTransaction()
558 switch(sendCoinsReturn.status)
560 case WalletModel::InvalidAddress:
561 msgParams.first = tr("The recipient address is not valid. Please recheck.");
562 break;
563 case WalletModel::InvalidAmount:
564 msgParams.first = tr("The amount to pay must be larger than 0.");
565 break;
566 case WalletModel::AmountExceedsBalance:
567 msgParams.first = tr("The amount exceeds your balance.");
568 break;
569 case WalletModel::AmountWithFeeExceedsBalance:
570 msgParams.first = tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg);
571 break;
572 case WalletModel::DuplicateAddress:
573 msgParams.first = tr("Duplicate address found: addresses should only be used once each.");
574 break;
575 case WalletModel::TransactionCreationFailed:
576 msgParams.first = tr("Transaction creation failed!");
577 msgParams.second = CClientUIInterface::MSG_ERROR;
578 break;
579 case WalletModel::TransactionCommitFailed:
580 msgParams.first = tr("The transaction was rejected with the following reason: %1").arg(sendCoinsReturn.reasonCommitFailed);
581 msgParams.second = CClientUIInterface::MSG_ERROR;
582 break;
583 case WalletModel::AbsurdFee:
584 msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), maxTxFee));
585 break;
586 case WalletModel::PaymentRequestExpired:
587 msgParams.first = tr("Payment request expired.");
588 msgParams.second = CClientUIInterface::MSG_ERROR;
589 break;
590 // included to prevent a compiler warning.
591 case WalletModel::OK:
592 default:
593 return;
596 Q_EMIT message(tr("Send Coins"), msgParams.first, msgParams.second);
599 void SendCoinsDialog::minimizeFeeSection(bool fMinimize)
601 ui->labelFeeMinimized->setVisible(fMinimize);
602 ui->buttonChooseFee ->setVisible(fMinimize);
603 ui->buttonMinimizeFee->setVisible(!fMinimize);
604 ui->frameFeeSelection->setVisible(!fMinimize);
605 ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0);
606 fFeeMinimized = fMinimize;
609 void SendCoinsDialog::on_buttonChooseFee_clicked()
611 minimizeFeeSection(false);
614 void SendCoinsDialog::on_buttonMinimizeFee_clicked()
616 updateFeeMinimizedLabel();
617 minimizeFeeSection(true);
620 void SendCoinsDialog::setMinimumFee()
622 ui->radioCustomPerKilobyte->setChecked(true);
623 ui->customFee->setValue(CWallet::GetRequiredFee(1000));
626 void SendCoinsDialog::updateFeeSectionControls()
628 ui->confTargetSelector ->setEnabled(ui->radioSmartFee->isChecked());
629 ui->labelSmartFee ->setEnabled(ui->radioSmartFee->isChecked());
630 ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked());
631 ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked());
632 ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked());
633 ui->checkBoxMinimumFee ->setEnabled(ui->radioCustomFee->isChecked());
634 ui->labelMinFeeWarning ->setEnabled(ui->radioCustomFee->isChecked());
635 ui->radioCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked());
636 ui->customFee ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked());
639 void SendCoinsDialog::updateGlobalFeeVariables()
641 if (ui->radioSmartFee->isChecked())
643 payTxFee = CFeeRate(0);
645 else
647 payTxFee = CFeeRate(ui->customFee->value());
651 void SendCoinsDialog::updateFeeMinimizedLabel()
653 if(!model || !model->getOptionsModel())
654 return;
656 if (ui->radioSmartFee->isChecked())
657 ui->labelFeeMinimized->setText(ui->labelSmartFee->text());
658 else {
659 ui->labelFeeMinimized->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), ui->customFee->value()) +
660 ((ui->radioCustomPerKilobyte->isChecked()) ? "/kB" : ""));
664 void SendCoinsDialog::updateMinFeeLabel()
666 if (model && model->getOptionsModel())
667 ui->checkBoxMinimumFee->setText(tr("Pay only the required fee of %1").arg(
668 BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), CWallet::GetRequiredFee(1000)) + "/kB")
672 void SendCoinsDialog::updateSmartFeeLabel()
674 if(!model || !model->getOptionsModel())
675 return;
677 int nBlocksToConfirm = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
678 FeeCalculation feeCalc;
679 bool conservative_estimate = CalculateEstimateType(FeeEstimateMode::UNSET, ui->optInRBF->isChecked());
680 CFeeRate feeRate = ::feeEstimator.estimateSmartFee(nBlocksToConfirm, &feeCalc, ::mempool, conservative_estimate);
681 if (feeRate <= CFeeRate(0)) // not enough data => minfee
683 ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(),
684 std::max(CWallet::fallbackFee.GetFeePerK(), CWallet::GetRequiredFee(1000))) + "/kB");
685 ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...)
686 ui->labelFeeEstimation->setText("");
687 ui->fallbackFeeWarningLabel->setVisible(true);
688 int lightness = ui->fallbackFeeWarningLabel->palette().color(QPalette::WindowText).lightness();
689 QColor warning_colour(255 - (lightness / 5), 176 - (lightness / 3), 48 - (lightness / 14));
690 ui->fallbackFeeWarningLabel->setStyleSheet("QLabel { color: " + warning_colour.name() + "; }");
691 ui->fallbackFeeWarningLabel->setIndent(QFontMetrics(ui->fallbackFeeWarningLabel->font()).width("x"));
693 else
695 ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(),
696 std::max(feeRate.GetFeePerK(), CWallet::GetRequiredFee(1000))) + "/kB");
697 ui->labelSmartFee2->hide();
698 ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", feeCalc.returnedTarget));
699 ui->fallbackFeeWarningLabel->setVisible(false);
702 updateFeeMinimizedLabel();
705 // Coin Control: copy label "Quantity" to clipboard
706 void SendCoinsDialog::coinControlClipboardQuantity()
708 GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
711 // Coin Control: copy label "Amount" to clipboard
712 void SendCoinsDialog::coinControlClipboardAmount()
714 GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
717 // Coin Control: copy label "Fee" to clipboard
718 void SendCoinsDialog::coinControlClipboardFee()
720 GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
723 // Coin Control: copy label "After fee" to clipboard
724 void SendCoinsDialog::coinControlClipboardAfterFee()
726 GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
729 // Coin Control: copy label "Bytes" to clipboard
730 void SendCoinsDialog::coinControlClipboardBytes()
732 GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, ""));
735 // Coin Control: copy label "Dust" to clipboard
736 void SendCoinsDialog::coinControlClipboardLowOutput()
738 GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text());
741 // Coin Control: copy label "Change" to clipboard
742 void SendCoinsDialog::coinControlClipboardChange()
744 GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
747 // Coin Control: settings menu - coin control enabled/disabled by user
748 void SendCoinsDialog::coinControlFeatureChanged(bool checked)
750 ui->frameCoinControl->setVisible(checked);
752 if (!checked && model) // coin control features disabled
753 CoinControlDialog::coinControl->SetNull();
755 // make sure we set back the confirmation target
756 updateGlobalFeeVariables();
757 coinControlUpdateLabels();
760 // Coin Control: button inputs -> show actual coin control dialog
761 void SendCoinsDialog::coinControlButtonClicked()
763 CoinControlDialog dlg(platformStyle);
764 dlg.setModel(model);
765 dlg.exec();
766 coinControlUpdateLabels();
769 // Coin Control: checkbox custom change address
770 void SendCoinsDialog::coinControlChangeChecked(int state)
772 if (state == Qt::Unchecked)
774 CoinControlDialog::coinControl->destChange = CNoDestination();
775 ui->labelCoinControlChangeLabel->clear();
777 else
778 // use this to re-validate an already entered address
779 coinControlChangeEdited(ui->lineEditCoinControlChange->text());
781 ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
784 // Coin Control: custom change address changed
785 void SendCoinsDialog::coinControlChangeEdited(const QString& text)
787 if (model && model->getAddressTableModel())
789 // Default to no change address until verified
790 CoinControlDialog::coinControl->destChange = CNoDestination();
791 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}");
793 CBitcoinAddress addr = CBitcoinAddress(text.toStdString());
795 if (text.isEmpty()) // Nothing entered
797 ui->labelCoinControlChangeLabel->setText("");
799 else if (!addr.IsValid()) // Invalid address
801 ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Bitcoin address"));
803 else // Valid address
805 CKeyID keyid;
806 addr.GetKeyID(keyid);
807 if (!model->havePrivKey(keyid)) // Unknown change address
809 ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
811 // confirmation dialog
812 QMessageBox::StandardButton btnRetVal = QMessageBox::question(this, tr("Confirm custom change address"), tr("The address you selected for change is not part of this wallet. Any or all funds in your wallet may be sent to this address. Are you sure?"),
813 QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
815 if(btnRetVal == QMessageBox::Yes)
816 CoinControlDialog::coinControl->destChange = addr.Get();
817 else
819 ui->lineEditCoinControlChange->setText("");
820 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
821 ui->labelCoinControlChangeLabel->setText("");
824 else // Known change address
826 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
828 // Query label
829 QString associatedLabel = model->getAddressTableModel()->labelForAddress(text);
830 if (!associatedLabel.isEmpty())
831 ui->labelCoinControlChangeLabel->setText(associatedLabel);
832 else
833 ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
835 CoinControlDialog::coinControl->destChange = addr.Get();
841 // Coin Control: update labels
842 void SendCoinsDialog::coinControlUpdateLabels()
844 if (!model || !model->getOptionsModel())
845 return;
847 // set pay amounts
848 CoinControlDialog::payAmounts.clear();
849 CoinControlDialog::fSubtractFeeFromAmount = false;
850 if (ui->radioSmartFee->isChecked()) {
851 CoinControlDialog::coinControl->nConfirmTarget = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
852 } else {
853 CoinControlDialog::coinControl->nConfirmTarget = model->getDefaultConfirmTarget();
855 CoinControlDialog::coinControl->signalRbf = ui->optInRBF->isChecked();
857 for(int i = 0; i < ui->entries->count(); ++i)
859 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
860 if(entry && !entry->isHidden())
862 SendCoinsRecipient rcp = entry->getValue();
863 CoinControlDialog::payAmounts.append(rcp.amount);
864 if (rcp.fSubtractFeeFromAmount)
865 CoinControlDialog::fSubtractFeeFromAmount = true;
869 if (CoinControlDialog::coinControl->HasSelected())
871 // actual coin control calculation
872 CoinControlDialog::updateLabels(model, this);
874 // show coin control stats
875 ui->labelCoinControlAutomaticallySelected->hide();
876 ui->widgetCoinControl->show();
878 else
880 // hide coin control stats
881 ui->labelCoinControlAutomaticallySelected->show();
882 ui->widgetCoinControl->hide();
883 ui->labelCoinControlInsuffFunds->hide();
887 SendConfirmationDialog::SendConfirmationDialog(const QString &title, const QString &text, int _secDelay,
888 QWidget *parent) :
889 QMessageBox(QMessageBox::Question, title, text, QMessageBox::Yes | QMessageBox::Cancel, parent), secDelay(_secDelay)
891 setDefaultButton(QMessageBox::Cancel);
892 yesButton = button(QMessageBox::Yes);
893 updateYesButton();
894 connect(&countDownTimer, SIGNAL(timeout()), this, SLOT(countDown()));
897 int SendConfirmationDialog::exec()
899 updateYesButton();
900 countDownTimer.start(1000);
901 return QMessageBox::exec();
904 void SendConfirmationDialog::countDown()
906 secDelay--;
907 updateYesButton();
909 if(secDelay <= 0)
911 countDownTimer.stop();
915 void SendConfirmationDialog::updateYesButton()
917 if(secDelay > 0)
919 yesButton->setEnabled(false);
920 yesButton->setText(tr("Yes") + " (" + QString::number(secDelay) + ")");
922 else
924 yesButton->setEnabled(true);
925 yesButton->setText(tr("Yes"));