Remove unused variable int64_t nEnd
[bitcoinplatinum.git] / src / qt / sendcoinsdialog.cpp
bloba01886c3ea8aefbe9026e04e9c2d3d06e3781700
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(coinControlUpdateLabels()));
179 connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(updateFeeSectionControls()));
180 connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels()));
181 connect(ui->groupCustomFee, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels()));
182 connect(ui->customFee, SIGNAL(valueChanged()), this, SLOT(coinControlUpdateLabels()));
183 connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(setMinimumFee()));
184 connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateFeeSectionControls()));
185 connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels()));
186 connect(ui->optInRBF, SIGNAL(stateChanged(int)), this, SLOT(updateSmartFeeLabel()));
187 connect(ui->optInRBF, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels()));
188 ui->customFee->setSingleStep(CWallet::GetRequiredFee(1000));
189 updateFeeSectionControls();
190 updateMinFeeLabel();
191 updateSmartFeeLabel();
193 // set default rbf checkbox state
194 ui->optInRBF->setCheckState(model->getDefaultWalletRbf() ? Qt::Checked : Qt::Unchecked);
196 // set the smartfee-sliders default value (wallets default conf.target or last stored value)
197 QSettings settings;
198 if (settings.value("nSmartFeeSliderPosition").toInt() != 0) {
199 // migrate nSmartFeeSliderPosition to nConfTarget
200 // nConfTarget is available since 0.15 (replaced nSmartFeeSliderPosition)
201 int nConfirmTarget = 25 - settings.value("nSmartFeeSliderPosition").toInt(); // 25 == old slider range
202 settings.setValue("nConfTarget", nConfirmTarget);
203 settings.remove("nSmartFeeSliderPosition");
205 if (settings.value("nConfTarget").toInt() == 0)
206 ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(model->getDefaultConfirmTarget()));
207 else
208 ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(settings.value("nConfTarget").toInt()));
212 SendCoinsDialog::~SendCoinsDialog()
214 QSettings settings;
215 settings.setValue("fFeeSectionMinimized", fFeeMinimized);
216 settings.setValue("nFeeRadio", ui->groupFee->checkedId());
217 settings.setValue("nCustomFeeRadio", ui->groupCustomFee->checkedId());
218 settings.setValue("nConfTarget", getConfTargetForIndex(ui->confTargetSelector->currentIndex()));
219 settings.setValue("nTransactionFee", (qint64)ui->customFee->value());
220 settings.setValue("fPayOnlyMinFee", ui->checkBoxMinimumFee->isChecked());
222 delete ui;
225 void SendCoinsDialog::on_sendButton_clicked()
227 if(!model || !model->getOptionsModel())
228 return;
230 QList<SendCoinsRecipient> recipients;
231 bool valid = true;
233 for(int i = 0; i < ui->entries->count(); ++i)
235 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
236 if(entry)
238 if(entry->validate())
240 recipients.append(entry->getValue());
242 else
244 valid = false;
249 if(!valid || recipients.isEmpty())
251 return;
254 fNewRecipientAllowed = false;
255 WalletModel::UnlockContext ctx(model->requestUnlock());
256 if(!ctx.isValid())
258 // Unlock wallet was cancelled
259 fNewRecipientAllowed = true;
260 return;
263 // prepare transaction for getting txFee earlier
264 WalletModelTransaction currentTransaction(recipients);
265 WalletModel::SendCoinsReturn prepareStatus;
267 // Always use a CCoinControl instance, use the CoinControlDialog instance if CoinControl has been enabled
268 CCoinControl ctrl;
269 if (model->getOptionsModel()->getCoinControlFeatures())
270 ctrl = *CoinControlDialog::coinControl;
272 updateCoinControlState(ctrl);
274 prepareStatus = model->prepareTransaction(currentTransaction, ctrl);
276 // process prepareStatus and on error generate message shown to user
277 processSendCoinsReturn(prepareStatus,
278 BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTransactionFee()));
280 if(prepareStatus.status != WalletModel::OK) {
281 fNewRecipientAllowed = true;
282 return;
285 CAmount txFee = currentTransaction.getTransactionFee();
287 // Format confirmation message
288 QStringList formatted;
289 for (const SendCoinsRecipient &rcp : currentTransaction.getRecipients())
291 // generate bold amount string
292 QString amount = "<b>" + BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
293 amount.append("</b>");
294 // generate monospace address string
295 QString address = "<span style='font-family: monospace;'>" + rcp.address;
296 address.append("</span>");
298 QString recipientElement;
300 if (!rcp.paymentRequest.IsInitialized()) // normal payment
302 if(rcp.label.length() > 0) // label with address
304 recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.label));
305 recipientElement.append(QString(" (%1)").arg(address));
307 else // just address
309 recipientElement = tr("%1 to %2").arg(amount, address);
312 else if(!rcp.authenticatedMerchant.isEmpty()) // authenticated payment request
314 recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.authenticatedMerchant));
316 else // unauthenticated payment request
318 recipientElement = tr("%1 to %2").arg(amount, address);
321 formatted.append(recipientElement);
324 QString questionString = tr("Are you sure you want to send?");
325 questionString.append("<br /><br />%1");
327 if(txFee > 0)
329 // append fee string if a fee is required
330 questionString.append("<hr /><span style='color:#aa0000;'>");
331 questionString.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee));
332 questionString.append("</span> ");
333 questionString.append(tr("added as transaction fee"));
335 // append transaction size
336 questionString.append(" (" + QString::number((double)currentTransaction.getTransactionSize() / 1000) + " kB)");
339 // add total amount in all subdivision units
340 questionString.append("<hr />");
341 CAmount totalAmount = currentTransaction.getTotalTransactionAmount() + txFee;
342 QStringList alternativeUnits;
343 for (BitcoinUnits::Unit u : BitcoinUnits::availableUnits())
345 if(u != model->getOptionsModel()->getDisplayUnit())
346 alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
348 questionString.append(tr("Total Amount %1")
349 .arg(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), totalAmount)));
350 questionString.append(QString("<span style='font-size:10pt;font-weight:normal;'><br />(=%2)</span>")
351 .arg(alternativeUnits.join(" " + tr("or") + "<br />")));
353 if (ui->optInRBF->isChecked())
355 questionString.append("<hr /><span>");
356 questionString.append(tr("This transaction signals replaceability (optin-RBF)."));
357 questionString.append("</span>");
360 SendConfirmationDialog confirmationDialog(tr("Confirm send coins"),
361 questionString.arg(formatted.join("<br />")), SEND_CONFIRM_DELAY, this);
362 confirmationDialog.exec();
363 QMessageBox::StandardButton retval = (QMessageBox::StandardButton)confirmationDialog.result();
365 if(retval != QMessageBox::Yes)
367 fNewRecipientAllowed = true;
368 return;
371 // now send the prepared transaction
372 WalletModel::SendCoinsReturn sendStatus = model->sendCoins(currentTransaction);
373 // process sendStatus and on error generate message shown to user
374 processSendCoinsReturn(sendStatus);
376 if (sendStatus.status == WalletModel::OK)
378 accept();
379 CoinControlDialog::coinControl->UnSelectAll();
380 coinControlUpdateLabels();
382 fNewRecipientAllowed = true;
385 void SendCoinsDialog::clear()
387 // Remove entries until only one left
388 while(ui->entries->count())
390 ui->entries->takeAt(0)->widget()->deleteLater();
392 addEntry();
394 updateTabsAndLabels();
397 void SendCoinsDialog::reject()
399 clear();
402 void SendCoinsDialog::accept()
404 clear();
407 SendCoinsEntry *SendCoinsDialog::addEntry()
409 SendCoinsEntry *entry = new SendCoinsEntry(platformStyle, this);
410 entry->setModel(model);
411 ui->entries->addWidget(entry);
412 connect(entry, SIGNAL(removeEntry(SendCoinsEntry*)), this, SLOT(removeEntry(SendCoinsEntry*)));
413 connect(entry, SIGNAL(payAmountChanged()), this, SLOT(coinControlUpdateLabels()));
414 connect(entry, SIGNAL(subtractFeeFromAmountChanged()), this, SLOT(coinControlUpdateLabels()));
416 // Focus the field, so that entry can start immediately
417 entry->clear();
418 entry->setFocus();
419 ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
420 qApp->processEvents();
421 QScrollBar* bar = ui->scrollArea->verticalScrollBar();
422 if(bar)
423 bar->setSliderPosition(bar->maximum());
425 updateTabsAndLabels();
426 return entry;
429 void SendCoinsDialog::updateTabsAndLabels()
431 setupTabChain(0);
432 coinControlUpdateLabels();
435 void SendCoinsDialog::removeEntry(SendCoinsEntry* entry)
437 entry->hide();
439 // If the last entry is about to be removed add an empty one
440 if (ui->entries->count() == 1)
441 addEntry();
443 entry->deleteLater();
445 updateTabsAndLabels();
448 QWidget *SendCoinsDialog::setupTabChain(QWidget *prev)
450 for(int i = 0; i < ui->entries->count(); ++i)
452 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
453 if(entry)
455 prev = entry->setupTabChain(prev);
458 QWidget::setTabOrder(prev, ui->sendButton);
459 QWidget::setTabOrder(ui->sendButton, ui->clearButton);
460 QWidget::setTabOrder(ui->clearButton, ui->addButton);
461 return ui->addButton;
464 void SendCoinsDialog::setAddress(const QString &address)
466 SendCoinsEntry *entry = 0;
467 // Replace the first entry if it is still unused
468 if(ui->entries->count() == 1)
470 SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
471 if(first->isClear())
473 entry = first;
476 if(!entry)
478 entry = addEntry();
481 entry->setAddress(address);
484 void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv)
486 if(!fNewRecipientAllowed)
487 return;
489 SendCoinsEntry *entry = 0;
490 // Replace the first entry if it is still unused
491 if(ui->entries->count() == 1)
493 SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
494 if(first->isClear())
496 entry = first;
499 if(!entry)
501 entry = addEntry();
504 entry->setValue(rv);
505 updateTabsAndLabels();
508 bool SendCoinsDialog::handlePaymentRequest(const SendCoinsRecipient &rv)
510 // Just paste the entry, all pre-checks
511 // are done in paymentserver.cpp.
512 pasteEntry(rv);
513 return true;
516 void SendCoinsDialog::setBalance(const CAmount& balance, const CAmount& unconfirmedBalance, const CAmount& immatureBalance,
517 const CAmount& watchBalance, const CAmount& watchUnconfirmedBalance, const CAmount& watchImmatureBalance)
519 Q_UNUSED(unconfirmedBalance);
520 Q_UNUSED(immatureBalance);
521 Q_UNUSED(watchBalance);
522 Q_UNUSED(watchUnconfirmedBalance);
523 Q_UNUSED(watchImmatureBalance);
525 if(model && model->getOptionsModel())
527 ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), balance));
531 void SendCoinsDialog::updateDisplayUnit()
533 setBalance(model->getBalance(), 0, 0, 0, 0, 0);
534 ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
535 updateMinFeeLabel();
536 updateSmartFeeLabel();
539 void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg)
541 QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
542 // Default to a warning message, override if error message is needed
543 msgParams.second = CClientUIInterface::MSG_WARNING;
545 // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn.
546 // WalletModel::TransactionCommitFailed is used only in WalletModel::sendCoins()
547 // all others are used only in WalletModel::prepareTransaction()
548 switch(sendCoinsReturn.status)
550 case WalletModel::InvalidAddress:
551 msgParams.first = tr("The recipient address is not valid. Please recheck.");
552 break;
553 case WalletModel::InvalidAmount:
554 msgParams.first = tr("The amount to pay must be larger than 0.");
555 break;
556 case WalletModel::AmountExceedsBalance:
557 msgParams.first = tr("The amount exceeds your balance.");
558 break;
559 case WalletModel::AmountWithFeeExceedsBalance:
560 msgParams.first = tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg);
561 break;
562 case WalletModel::DuplicateAddress:
563 msgParams.first = tr("Duplicate address found: addresses should only be used once each.");
564 break;
565 case WalletModel::TransactionCreationFailed:
566 msgParams.first = tr("Transaction creation failed!");
567 msgParams.second = CClientUIInterface::MSG_ERROR;
568 break;
569 case WalletModel::TransactionCommitFailed:
570 msgParams.first = tr("The transaction was rejected with the following reason: %1").arg(sendCoinsReturn.reasonCommitFailed);
571 msgParams.second = CClientUIInterface::MSG_ERROR;
572 break;
573 case WalletModel::AbsurdFee:
574 msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), maxTxFee));
575 break;
576 case WalletModel::PaymentRequestExpired:
577 msgParams.first = tr("Payment request expired.");
578 msgParams.second = CClientUIInterface::MSG_ERROR;
579 break;
580 // included to prevent a compiler warning.
581 case WalletModel::OK:
582 default:
583 return;
586 Q_EMIT message(tr("Send Coins"), msgParams.first, msgParams.second);
589 void SendCoinsDialog::minimizeFeeSection(bool fMinimize)
591 ui->labelFeeMinimized->setVisible(fMinimize);
592 ui->buttonChooseFee ->setVisible(fMinimize);
593 ui->buttonMinimizeFee->setVisible(!fMinimize);
594 ui->frameFeeSelection->setVisible(!fMinimize);
595 ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0);
596 fFeeMinimized = fMinimize;
599 void SendCoinsDialog::on_buttonChooseFee_clicked()
601 minimizeFeeSection(false);
604 void SendCoinsDialog::on_buttonMinimizeFee_clicked()
606 updateFeeMinimizedLabel();
607 minimizeFeeSection(true);
610 void SendCoinsDialog::setMinimumFee()
612 ui->radioCustomPerKilobyte->setChecked(true);
613 ui->customFee->setValue(CWallet::GetRequiredFee(1000));
616 void SendCoinsDialog::updateFeeSectionControls()
618 ui->confTargetSelector ->setEnabled(ui->radioSmartFee->isChecked());
619 ui->labelSmartFee ->setEnabled(ui->radioSmartFee->isChecked());
620 ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked());
621 ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked());
622 ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked());
623 ui->checkBoxMinimumFee ->setEnabled(ui->radioCustomFee->isChecked());
624 ui->labelMinFeeWarning ->setEnabled(ui->radioCustomFee->isChecked());
625 ui->radioCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked());
626 ui->customFee ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked());
629 void SendCoinsDialog::updateFeeMinimizedLabel()
631 if(!model || !model->getOptionsModel())
632 return;
634 if (ui->radioSmartFee->isChecked())
635 ui->labelFeeMinimized->setText(ui->labelSmartFee->text());
636 else {
637 ui->labelFeeMinimized->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), ui->customFee->value()) +
638 ((ui->radioCustomPerKilobyte->isChecked()) ? "/kB" : ""));
642 void SendCoinsDialog::updateMinFeeLabel()
644 if (model && model->getOptionsModel())
645 ui->checkBoxMinimumFee->setText(tr("Pay only the required fee of %1").arg(
646 BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), CWallet::GetRequiredFee(1000)) + "/kB")
650 void SendCoinsDialog::updateCoinControlState(CCoinControl& ctrl)
652 if (ui->radioCustomFee->isChecked()) {
653 ctrl.m_feerate = CFeeRate(ui->customFee->value());
654 } else {
655 ctrl.m_feerate.reset();
657 // Avoid using global defaults when sending money from the GUI
658 // Either custom fee will be used or if not selected, the confirmation target from dropdown box
659 ctrl.m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
660 ctrl.signalRbf = ui->optInRBF->isChecked();
663 void SendCoinsDialog::updateSmartFeeLabel()
665 if(!model || !model->getOptionsModel())
666 return;
667 CCoinControl coin_control;
668 updateCoinControlState(coin_control);
669 coin_control.m_feerate.reset(); // Explicitly use only fee estimation rate for smart fee labels
670 FeeCalculation feeCalc;
671 CFeeRate feeRate = CFeeRate(CWallet::GetMinimumFee(1000, coin_control, ::mempool, ::feeEstimator, &feeCalc));
673 ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), feeRate.GetFeePerK()) + "/kB");
675 if (feeCalc.reason == FeeReason::FALLBACK) {
676 ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...)
677 ui->labelFeeEstimation->setText("");
678 ui->fallbackFeeWarningLabel->setVisible(true);
679 int lightness = ui->fallbackFeeWarningLabel->palette().color(QPalette::WindowText).lightness();
680 QColor warning_colour(255 - (lightness / 5), 176 - (lightness / 3), 48 - (lightness / 14));
681 ui->fallbackFeeWarningLabel->setStyleSheet("QLabel { color: " + warning_colour.name() + "; }");
682 ui->fallbackFeeWarningLabel->setIndent(QFontMetrics(ui->fallbackFeeWarningLabel->font()).width("x"));
684 else
686 ui->labelSmartFee2->hide();
687 ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", feeCalc.returnedTarget));
688 ui->fallbackFeeWarningLabel->setVisible(false);
691 updateFeeMinimizedLabel();
694 // Coin Control: copy label "Quantity" to clipboard
695 void SendCoinsDialog::coinControlClipboardQuantity()
697 GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
700 // Coin Control: copy label "Amount" to clipboard
701 void SendCoinsDialog::coinControlClipboardAmount()
703 GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
706 // Coin Control: copy label "Fee" to clipboard
707 void SendCoinsDialog::coinControlClipboardFee()
709 GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
712 // Coin Control: copy label "After fee" to clipboard
713 void SendCoinsDialog::coinControlClipboardAfterFee()
715 GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
718 // Coin Control: copy label "Bytes" to clipboard
719 void SendCoinsDialog::coinControlClipboardBytes()
721 GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, ""));
724 // Coin Control: copy label "Dust" to clipboard
725 void SendCoinsDialog::coinControlClipboardLowOutput()
727 GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text());
730 // Coin Control: copy label "Change" to clipboard
731 void SendCoinsDialog::coinControlClipboardChange()
733 GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
736 // Coin Control: settings menu - coin control enabled/disabled by user
737 void SendCoinsDialog::coinControlFeatureChanged(bool checked)
739 ui->frameCoinControl->setVisible(checked);
741 if (!checked && model) // coin control features disabled
742 CoinControlDialog::coinControl->SetNull();
744 coinControlUpdateLabels();
747 // Coin Control: button inputs -> show actual coin control dialog
748 void SendCoinsDialog::coinControlButtonClicked()
750 CoinControlDialog dlg(platformStyle);
751 dlg.setModel(model);
752 dlg.exec();
753 coinControlUpdateLabels();
756 // Coin Control: checkbox custom change address
757 void SendCoinsDialog::coinControlChangeChecked(int state)
759 if (state == Qt::Unchecked)
761 CoinControlDialog::coinControl->destChange = CNoDestination();
762 ui->labelCoinControlChangeLabel->clear();
764 else
765 // use this to re-validate an already entered address
766 coinControlChangeEdited(ui->lineEditCoinControlChange->text());
768 ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
771 // Coin Control: custom change address changed
772 void SendCoinsDialog::coinControlChangeEdited(const QString& text)
774 if (model && model->getAddressTableModel())
776 // Default to no change address until verified
777 CoinControlDialog::coinControl->destChange = CNoDestination();
778 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}");
780 CBitcoinAddress addr = CBitcoinAddress(text.toStdString());
782 if (text.isEmpty()) // Nothing entered
784 ui->labelCoinControlChangeLabel->setText("");
786 else if (!addr.IsValid()) // Invalid address
788 ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Bitcoin address"));
790 else // Valid address
792 CKeyID keyid;
793 addr.GetKeyID(keyid);
794 if (!model->havePrivKey(keyid)) // Unknown change address
796 ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
798 // confirmation dialog
799 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?"),
800 QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
802 if(btnRetVal == QMessageBox::Yes)
803 CoinControlDialog::coinControl->destChange = addr.Get();
804 else
806 ui->lineEditCoinControlChange->setText("");
807 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
808 ui->labelCoinControlChangeLabel->setText("");
811 else // Known change address
813 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
815 // Query label
816 QString associatedLabel = model->getAddressTableModel()->labelForAddress(text);
817 if (!associatedLabel.isEmpty())
818 ui->labelCoinControlChangeLabel->setText(associatedLabel);
819 else
820 ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
822 CoinControlDialog::coinControl->destChange = addr.Get();
828 // Coin Control: update labels
829 void SendCoinsDialog::coinControlUpdateLabels()
831 if (!model || !model->getOptionsModel())
832 return;
834 updateCoinControlState(*CoinControlDialog::coinControl);
836 // set pay amounts
837 CoinControlDialog::payAmounts.clear();
838 CoinControlDialog::fSubtractFeeFromAmount = false;
840 for(int i = 0; i < ui->entries->count(); ++i)
842 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
843 if(entry && !entry->isHidden())
845 SendCoinsRecipient rcp = entry->getValue();
846 CoinControlDialog::payAmounts.append(rcp.amount);
847 if (rcp.fSubtractFeeFromAmount)
848 CoinControlDialog::fSubtractFeeFromAmount = true;
852 if (CoinControlDialog::coinControl->HasSelected())
854 // actual coin control calculation
855 CoinControlDialog::updateLabels(model, this);
857 // show coin control stats
858 ui->labelCoinControlAutomaticallySelected->hide();
859 ui->widgetCoinControl->show();
861 else
863 // hide coin control stats
864 ui->labelCoinControlAutomaticallySelected->show();
865 ui->widgetCoinControl->hide();
866 ui->labelCoinControlInsuffFunds->hide();
870 SendConfirmationDialog::SendConfirmationDialog(const QString &title, const QString &text, int _secDelay,
871 QWidget *parent) :
872 QMessageBox(QMessageBox::Question, title, text, QMessageBox::Yes | QMessageBox::Cancel, parent), secDelay(_secDelay)
874 setDefaultButton(QMessageBox::Cancel);
875 yesButton = button(QMessageBox::Yes);
876 updateYesButton();
877 connect(&countDownTimer, SIGNAL(timeout()), this, SLOT(countDown()));
880 int SendConfirmationDialog::exec()
882 updateYesButton();
883 countDownTimer.start(1000);
884 return QMessageBox::exec();
887 void SendConfirmationDialog::countDown()
889 secDelay--;
890 updateYesButton();
892 if(secDelay <= 0)
894 countDownTimer.stop();
898 void SendConfirmationDialog::updateYesButton()
900 if(secDelay > 0)
902 yesButton->setEnabled(false);
903 yesButton->setText(tr("Yes") + " (" + QString::number(secDelay) + ")");
905 else
907 yesButton->setEnabled(true);
908 yesButton->setText(tr("Yes"));