Replaces numbered place marker %2 with %1.
[bitcoinplatinum.git] / src / qt / sendcoinsdialog.cpp
blob9fd61db70efc939276d4f87858ddf36172d4710f
1 // Copyright (c) 2011-2017 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 <qt/sendcoinsdialog.h>
6 #include <qt/forms/ui_sendcoinsdialog.h>
8 #include <qt/addresstablemodel.h>
9 #include <qt/bitcoinunits.h>
10 #include <qt/clientmodel.h>
11 #include <qt/coincontroldialog.h>
12 #include <qt/guiutil.h>
13 #include <qt/optionsmodel.h>
14 #include <qt/platformstyle.h>
15 #include <qt/sendcoinsentry.h>
17 #include <base58.h>
18 #include <chainparams.h>
19 #include <wallet/coincontrol.h>
20 #include <validation.h> // mempool and minRelayTxFee
21 #include <ui_interface.h>
22 #include <txmempool.h>
23 #include <policy/fees.h>
24 #include <wallet/fees.h>
26 #include <QFontMetrics>
27 #include <QScrollBar>
28 #include <QSettings>
29 #include <QTextDocument>
31 static const std::array<int, 9> confTargets = { {2, 4, 6, 12, 24, 48, 144, 504, 1008} };
32 int getConfTargetForIndex(int index) {
33 if (index+1 > static_cast<int>(confTargets.size())) {
34 return confTargets.back();
36 if (index < 0) {
37 return confTargets[0];
39 return confTargets[index];
41 int getIndexForConfTarget(int target) {
42 for (unsigned int i = 0; i < confTargets.size(); i++) {
43 if (confTargets[i] >= target) {
44 return i;
47 return confTargets.size() - 1;
50 SendCoinsDialog::SendCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) :
51 QDialog(parent),
52 ui(new Ui::SendCoinsDialog),
53 clientModel(0),
54 model(0),
55 fNewRecipientAllowed(true),
56 fFeeMinimized(true),
57 platformStyle(_platformStyle)
59 ui->setupUi(this);
61 if (!_platformStyle->getImagesOnButtons()) {
62 ui->addButton->setIcon(QIcon());
63 ui->clearButton->setIcon(QIcon());
64 ui->sendButton->setIcon(QIcon());
65 } else {
66 ui->addButton->setIcon(_platformStyle->SingleColorIcon(":/icons/add"));
67 ui->clearButton->setIcon(_platformStyle->SingleColorIcon(":/icons/remove"));
68 ui->sendButton->setIcon(_platformStyle->SingleColorIcon(":/icons/send"));
71 GUIUtil::setupAddressWidget(ui->lineEditCoinControlChange, this);
73 addEntry();
75 connect(ui->addButton, SIGNAL(clicked()), this, SLOT(addEntry()));
76 connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear()));
78 // Coin Control
79 connect(ui->pushButtonCoinControl, SIGNAL(clicked()), this, SLOT(coinControlButtonClicked()));
80 connect(ui->checkBoxCoinControlChange, SIGNAL(stateChanged(int)), this, SLOT(coinControlChangeChecked(int)));
81 connect(ui->lineEditCoinControlChange, SIGNAL(textEdited(const QString &)), this, SLOT(coinControlChangeEdited(const QString &)));
83 // Coin Control: clipboard actions
84 QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this);
85 QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this);
86 QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this);
87 QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this);
88 QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this);
89 QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this);
90 QAction *clipboardChangeAction = new QAction(tr("Copy change"), this);
91 connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardQuantity()));
92 connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAmount()));
93 connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardFee()));
94 connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAfterFee()));
95 connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardBytes()));
96 connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardLowOutput()));
97 connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardChange()));
98 ui->labelCoinControlQuantity->addAction(clipboardQuantityAction);
99 ui->labelCoinControlAmount->addAction(clipboardAmountAction);
100 ui->labelCoinControlFee->addAction(clipboardFeeAction);
101 ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction);
102 ui->labelCoinControlBytes->addAction(clipboardBytesAction);
103 ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction);
104 ui->labelCoinControlChange->addAction(clipboardChangeAction);
106 // init transaction fee section
107 QSettings settings;
108 if (!settings.contains("fFeeSectionMinimized"))
109 settings.setValue("fFeeSectionMinimized", true);
110 if (!settings.contains("nFeeRadio") && settings.contains("nTransactionFee") && settings.value("nTransactionFee").toLongLong() > 0) // compatibility
111 settings.setValue("nFeeRadio", 1); // custom
112 if (!settings.contains("nFeeRadio"))
113 settings.setValue("nFeeRadio", 0); // recommended
114 if (!settings.contains("nSmartFeeSliderPosition"))
115 settings.setValue("nSmartFeeSliderPosition", 0);
116 if (!settings.contains("nTransactionFee"))
117 settings.setValue("nTransactionFee", (qint64)DEFAULT_TRANSACTION_FEE);
118 if (!settings.contains("fPayOnlyMinFee"))
119 settings.setValue("fPayOnlyMinFee", false);
120 ui->groupFee->setId(ui->radioSmartFee, 0);
121 ui->groupFee->setId(ui->radioCustomFee, 1);
122 ui->groupFee->button((int)std::max(0, std::min(1, settings.value("nFeeRadio").toInt())))->setChecked(true);
123 ui->customFee->setValue(settings.value("nTransactionFee").toLongLong());
124 ui->checkBoxMinimumFee->setChecked(settings.value("fPayOnlyMinFee").toBool());
125 minimizeFeeSection(settings.value("fFeeSectionMinimized").toBool());
128 void SendCoinsDialog::setClientModel(ClientModel *_clientModel)
130 this->clientModel = _clientModel;
132 if (_clientModel) {
133 connect(_clientModel, SIGNAL(numBlocksChanged(int,QDateTime,double,bool)), this, SLOT(updateSmartFeeLabel()));
137 void SendCoinsDialog::setModel(WalletModel *_model)
139 this->model = _model;
141 if(_model && _model->getOptionsModel())
143 for(int i = 0; i < ui->entries->count(); ++i)
145 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
146 if(entry)
148 entry->setModel(_model);
152 setBalance(_model->getBalance(), _model->getUnconfirmedBalance(), _model->getImmatureBalance(),
153 _model->getWatchBalance(), _model->getWatchUnconfirmedBalance(), _model->getWatchImmatureBalance());
154 connect(_model, SIGNAL(balanceChanged(CAmount,CAmount,CAmount,CAmount,CAmount,CAmount)), this, SLOT(setBalance(CAmount,CAmount,CAmount,CAmount,CAmount,CAmount)));
155 connect(_model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
156 updateDisplayUnit();
158 // Coin Control
159 connect(_model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(coinControlUpdateLabels()));
160 connect(_model->getOptionsModel(), SIGNAL(coinControlFeaturesChanged(bool)), this, SLOT(coinControlFeatureChanged(bool)));
161 ui->frameCoinControl->setVisible(_model->getOptionsModel()->getCoinControlFeatures());
162 coinControlUpdateLabels();
164 // fee section
165 for (const int n : confTargets) {
166 ui->confTargetSelector->addItem(tr("%1 (%2 blocks)").arg(GUIUtil::formatNiceTimeOffset(n*Params().GetConsensus().nPowTargetSpacing)).arg(n));
168 connect(ui->confTargetSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(updateSmartFeeLabel()));
169 connect(ui->confTargetSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(coinControlUpdateLabels()));
170 connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(updateFeeSectionControls()));
171 connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels()));
172 connect(ui->customFee, SIGNAL(valueChanged()), this, SLOT(coinControlUpdateLabels()));
173 connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(setMinimumFee()));
174 connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateFeeSectionControls()));
175 connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels()));
176 connect(ui->optInRBF, SIGNAL(stateChanged(int)), this, SLOT(updateSmartFeeLabel()));
177 connect(ui->optInRBF, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels()));
178 ui->customFee->setSingleStep(GetRequiredFee(1000));
179 updateFeeSectionControls();
180 updateMinFeeLabel();
181 updateSmartFeeLabel();
183 // set default rbf checkbox state
184 ui->optInRBF->setCheckState(Qt::Checked);
186 // set the smartfee-sliders default value (wallets default conf.target or last stored value)
187 QSettings settings;
188 if (settings.value("nSmartFeeSliderPosition").toInt() != 0) {
189 // migrate nSmartFeeSliderPosition to nConfTarget
190 // nConfTarget is available since 0.15 (replaced nSmartFeeSliderPosition)
191 int nConfirmTarget = 25 - settings.value("nSmartFeeSliderPosition").toInt(); // 25 == old slider range
192 settings.setValue("nConfTarget", nConfirmTarget);
193 settings.remove("nSmartFeeSliderPosition");
195 if (settings.value("nConfTarget").toInt() == 0)
196 ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(model->getDefaultConfirmTarget()));
197 else
198 ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(settings.value("nConfTarget").toInt()));
202 SendCoinsDialog::~SendCoinsDialog()
204 QSettings settings;
205 settings.setValue("fFeeSectionMinimized", fFeeMinimized);
206 settings.setValue("nFeeRadio", ui->groupFee->checkedId());
207 settings.setValue("nConfTarget", getConfTargetForIndex(ui->confTargetSelector->currentIndex()));
208 settings.setValue("nTransactionFee", (qint64)ui->customFee->value());
209 settings.setValue("fPayOnlyMinFee", ui->checkBoxMinimumFee->isChecked());
211 delete ui;
214 void SendCoinsDialog::on_sendButton_clicked()
216 if(!model || !model->getOptionsModel())
217 return;
219 QList<SendCoinsRecipient> recipients;
220 bool valid = true;
222 for(int i = 0; i < ui->entries->count(); ++i)
224 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
225 if(entry)
227 if(entry->validate())
229 recipients.append(entry->getValue());
231 else
233 valid = false;
238 if(!valid || recipients.isEmpty())
240 return;
243 fNewRecipientAllowed = false;
244 WalletModel::UnlockContext ctx(model->requestUnlock());
245 if(!ctx.isValid())
247 // Unlock wallet was cancelled
248 fNewRecipientAllowed = true;
249 return;
252 // prepare transaction for getting txFee earlier
253 WalletModelTransaction currentTransaction(recipients);
254 WalletModel::SendCoinsReturn prepareStatus;
256 // Always use a CCoinControl instance, use the CoinControlDialog instance if CoinControl has been enabled
257 CCoinControl ctrl;
258 if (model->getOptionsModel()->getCoinControlFeatures())
259 ctrl = *CoinControlDialog::coinControl;
261 updateCoinControlState(ctrl);
263 prepareStatus = model->prepareTransaction(currentTransaction, ctrl);
265 // process prepareStatus and on error generate message shown to user
266 processSendCoinsReturn(prepareStatus,
267 BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTransactionFee()));
269 if(prepareStatus.status != WalletModel::OK) {
270 fNewRecipientAllowed = true;
271 return;
274 CAmount txFee = currentTransaction.getTransactionFee();
276 // Format confirmation message
277 QStringList formatted;
278 for (const SendCoinsRecipient &rcp : currentTransaction.getRecipients())
280 // generate bold amount string
281 QString amount = "<b>" + BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
282 amount.append("</b>");
283 // generate monospace address string
284 QString address = "<span style='font-family: monospace;'>" + rcp.address;
285 address.append("</span>");
287 QString recipientElement;
289 if (!rcp.paymentRequest.IsInitialized()) // normal payment
291 if(rcp.label.length() > 0) // label with address
293 recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.label));
294 recipientElement.append(QString(" (%1)").arg(address));
296 else // just address
298 recipientElement = tr("%1 to %2").arg(amount, address);
301 else if(!rcp.authenticatedMerchant.isEmpty()) // authenticated payment request
303 recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.authenticatedMerchant));
305 else // unauthenticated payment request
307 recipientElement = tr("%1 to %2").arg(amount, address);
310 formatted.append(recipientElement);
313 QString questionString = tr("Are you sure you want to send?");
314 questionString.append("<br /><br />%1");
316 if(txFee > 0)
318 // append fee string if a fee is required
319 questionString.append("<hr /><span style='color:#aa0000;'>");
320 questionString.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee));
321 questionString.append("</span> ");
322 questionString.append(tr("added as transaction fee"));
324 // append transaction size
325 questionString.append(" (" + QString::number((double)currentTransaction.getTransactionSize() / 1000) + " kB)");
328 // add total amount in all subdivision units
329 questionString.append("<hr />");
330 CAmount totalAmount = currentTransaction.getTotalTransactionAmount() + txFee;
331 QStringList alternativeUnits;
332 for (BitcoinUnits::Unit u : BitcoinUnits::availableUnits())
334 if(u != model->getOptionsModel()->getDisplayUnit())
335 alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
337 questionString.append(tr("Total Amount %1")
338 .arg(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), totalAmount)));
339 questionString.append(QString("<span style='font-size:10pt;font-weight:normal;'><br />(=%1)</span>")
340 .arg(alternativeUnits.join(" " + tr("or") + "<br />")));
342 questionString.append("<hr /><span>");
343 if (ui->optInRBF->isChecked()) {
344 questionString.append(tr("You can increase the fee later (signals Replace-By-Fee, BIP-125)."));
345 } else {
346 questionString.append(tr("Not signalling Replace-By-Fee, BIP-125."));
348 questionString.append("</span>");
351 SendConfirmationDialog confirmationDialog(tr("Confirm send coins"),
352 questionString.arg(formatted.join("<br />")), SEND_CONFIRM_DELAY, this);
353 confirmationDialog.exec();
354 QMessageBox::StandardButton retval = (QMessageBox::StandardButton)confirmationDialog.result();
356 if(retval != QMessageBox::Yes)
358 fNewRecipientAllowed = true;
359 return;
362 // now send the prepared transaction
363 WalletModel::SendCoinsReturn sendStatus = model->sendCoins(currentTransaction);
364 // process sendStatus and on error generate message shown to user
365 processSendCoinsReturn(sendStatus);
367 if (sendStatus.status == WalletModel::OK)
369 accept();
370 CoinControlDialog::coinControl->UnSelectAll();
371 coinControlUpdateLabels();
373 fNewRecipientAllowed = true;
376 void SendCoinsDialog::clear()
378 // Remove entries until only one left
379 while(ui->entries->count())
381 ui->entries->takeAt(0)->widget()->deleteLater();
383 addEntry();
385 updateTabsAndLabels();
388 void SendCoinsDialog::reject()
390 clear();
393 void SendCoinsDialog::accept()
395 clear();
398 SendCoinsEntry *SendCoinsDialog::addEntry()
400 SendCoinsEntry *entry = new SendCoinsEntry(platformStyle, this);
401 entry->setModel(model);
402 ui->entries->addWidget(entry);
403 connect(entry, SIGNAL(removeEntry(SendCoinsEntry*)), this, SLOT(removeEntry(SendCoinsEntry*)));
404 connect(entry, SIGNAL(useAvailableBalance(SendCoinsEntry*)), this, SLOT(useAvailableBalance(SendCoinsEntry*)));
405 connect(entry, SIGNAL(payAmountChanged()), this, SLOT(coinControlUpdateLabels()));
406 connect(entry, SIGNAL(subtractFeeFromAmountChanged()), this, SLOT(coinControlUpdateLabels()));
408 // Focus the field, so that entry can start immediately
409 entry->clear();
410 entry->setFocus();
411 ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
412 qApp->processEvents();
413 QScrollBar* bar = ui->scrollArea->verticalScrollBar();
414 if(bar)
415 bar->setSliderPosition(bar->maximum());
417 updateTabsAndLabels();
418 return entry;
421 void SendCoinsDialog::updateTabsAndLabels()
423 setupTabChain(0);
424 coinControlUpdateLabels();
427 void SendCoinsDialog::removeEntry(SendCoinsEntry* entry)
429 entry->hide();
431 // If the last entry is about to be removed add an empty one
432 if (ui->entries->count() == 1)
433 addEntry();
435 entry->deleteLater();
437 updateTabsAndLabels();
440 QWidget *SendCoinsDialog::setupTabChain(QWidget *prev)
442 for(int i = 0; i < ui->entries->count(); ++i)
444 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
445 if(entry)
447 prev = entry->setupTabChain(prev);
450 QWidget::setTabOrder(prev, ui->sendButton);
451 QWidget::setTabOrder(ui->sendButton, ui->clearButton);
452 QWidget::setTabOrder(ui->clearButton, ui->addButton);
453 return ui->addButton;
456 void SendCoinsDialog::setAddress(const QString &address)
458 SendCoinsEntry *entry = 0;
459 // Replace the first entry if it is still unused
460 if(ui->entries->count() == 1)
462 SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
463 if(first->isClear())
465 entry = first;
468 if(!entry)
470 entry = addEntry();
473 entry->setAddress(address);
476 void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv)
478 if(!fNewRecipientAllowed)
479 return;
481 SendCoinsEntry *entry = 0;
482 // Replace the first entry if it is still unused
483 if(ui->entries->count() == 1)
485 SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
486 if(first->isClear())
488 entry = first;
491 if(!entry)
493 entry = addEntry();
496 entry->setValue(rv);
497 updateTabsAndLabels();
500 bool SendCoinsDialog::handlePaymentRequest(const SendCoinsRecipient &rv)
502 // Just paste the entry, all pre-checks
503 // are done in paymentserver.cpp.
504 pasteEntry(rv);
505 return true;
508 void SendCoinsDialog::setBalance(const CAmount& balance, const CAmount& unconfirmedBalance, const CAmount& immatureBalance,
509 const CAmount& watchBalance, const CAmount& watchUnconfirmedBalance, const CAmount& watchImmatureBalance)
511 Q_UNUSED(unconfirmedBalance);
512 Q_UNUSED(immatureBalance);
513 Q_UNUSED(watchBalance);
514 Q_UNUSED(watchUnconfirmedBalance);
515 Q_UNUSED(watchImmatureBalance);
517 if(model && model->getOptionsModel())
519 ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), balance));
523 void SendCoinsDialog::updateDisplayUnit()
525 setBalance(model->getBalance(), 0, 0, 0, 0, 0);
526 ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
527 updateMinFeeLabel();
528 updateSmartFeeLabel();
531 void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg)
533 QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
534 // Default to a warning message, override if error message is needed
535 msgParams.second = CClientUIInterface::MSG_WARNING;
537 // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn.
538 // WalletModel::TransactionCommitFailed is used only in WalletModel::sendCoins()
539 // all others are used only in WalletModel::prepareTransaction()
540 switch(sendCoinsReturn.status)
542 case WalletModel::InvalidAddress:
543 msgParams.first = tr("The recipient address is not valid. Please recheck.");
544 break;
545 case WalletModel::InvalidAmount:
546 msgParams.first = tr("The amount to pay must be larger than 0.");
547 break;
548 case WalletModel::AmountExceedsBalance:
549 msgParams.first = tr("The amount exceeds your balance.");
550 break;
551 case WalletModel::AmountWithFeeExceedsBalance:
552 msgParams.first = tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg);
553 break;
554 case WalletModel::DuplicateAddress:
555 msgParams.first = tr("Duplicate address found: addresses should only be used once each.");
556 break;
557 case WalletModel::TransactionCreationFailed:
558 msgParams.first = tr("Transaction creation failed!");
559 msgParams.second = CClientUIInterface::MSG_ERROR;
560 break;
561 case WalletModel::TransactionCommitFailed:
562 msgParams.first = tr("The transaction was rejected with the following reason: %1").arg(sendCoinsReturn.reasonCommitFailed);
563 msgParams.second = CClientUIInterface::MSG_ERROR;
564 break;
565 case WalletModel::AbsurdFee:
566 msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), maxTxFee));
567 break;
568 case WalletModel::PaymentRequestExpired:
569 msgParams.first = tr("Payment request expired.");
570 msgParams.second = CClientUIInterface::MSG_ERROR;
571 break;
572 // included to prevent a compiler warning.
573 case WalletModel::OK:
574 default:
575 return;
578 Q_EMIT message(tr("Send Coins"), msgParams.first, msgParams.second);
581 void SendCoinsDialog::minimizeFeeSection(bool fMinimize)
583 ui->labelFeeMinimized->setVisible(fMinimize);
584 ui->buttonChooseFee ->setVisible(fMinimize);
585 ui->buttonMinimizeFee->setVisible(!fMinimize);
586 ui->frameFeeSelection->setVisible(!fMinimize);
587 ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0);
588 fFeeMinimized = fMinimize;
591 void SendCoinsDialog::on_buttonChooseFee_clicked()
593 minimizeFeeSection(false);
596 void SendCoinsDialog::on_buttonMinimizeFee_clicked()
598 updateFeeMinimizedLabel();
599 minimizeFeeSection(true);
602 void SendCoinsDialog::useAvailableBalance(SendCoinsEntry* entry)
604 // Get CCoinControl instance if CoinControl is enabled or create a new one.
605 CCoinControl coin_control;
606 if (model->getOptionsModel()->getCoinControlFeatures()) {
607 coin_control = *CoinControlDialog::coinControl;
610 // Calculate available amount to send.
611 CAmount amount = model->getBalance(&coin_control);
612 for (int i = 0; i < ui->entries->count(); ++i) {
613 SendCoinsEntry* e = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
614 if (e && !e->isHidden() && e != entry) {
615 amount -= e->getValue().amount;
619 if (amount > 0) {
620 entry->checkSubtractFeeFromAmount();
621 entry->setAmount(amount);
622 } else {
623 entry->setAmount(0);
627 void SendCoinsDialog::setMinimumFee()
629 ui->customFee->setValue(GetRequiredFee(1000));
632 void SendCoinsDialog::updateFeeSectionControls()
634 ui->confTargetSelector ->setEnabled(ui->radioSmartFee->isChecked());
635 ui->labelSmartFee ->setEnabled(ui->radioSmartFee->isChecked());
636 ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked());
637 ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked());
638 ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked());
639 ui->checkBoxMinimumFee ->setEnabled(ui->radioCustomFee->isChecked());
640 ui->labelMinFeeWarning ->setEnabled(ui->radioCustomFee->isChecked());
641 ui->labelCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked());
642 ui->customFee ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked());
645 void SendCoinsDialog::updateFeeMinimizedLabel()
647 if(!model || !model->getOptionsModel())
648 return;
650 if (ui->radioSmartFee->isChecked())
651 ui->labelFeeMinimized->setText(ui->labelSmartFee->text());
652 else {
653 ui->labelFeeMinimized->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), ui->customFee->value()) + "/kB");
657 void SendCoinsDialog::updateMinFeeLabel()
659 if (model && model->getOptionsModel())
660 ui->checkBoxMinimumFee->setText(tr("Pay only the required fee of %1").arg(
661 BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), GetRequiredFee(1000)) + "/kB")
665 void SendCoinsDialog::updateCoinControlState(CCoinControl& ctrl)
667 if (ui->radioCustomFee->isChecked()) {
668 ctrl.m_feerate = CFeeRate(ui->customFee->value());
669 } else {
670 ctrl.m_feerate.reset();
672 // Avoid using global defaults when sending money from the GUI
673 // Either custom fee will be used or if not selected, the confirmation target from dropdown box
674 ctrl.m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
675 ctrl.signalRbf = ui->optInRBF->isChecked();
678 void SendCoinsDialog::updateSmartFeeLabel()
680 if(!model || !model->getOptionsModel())
681 return;
682 CCoinControl coin_control;
683 updateCoinControlState(coin_control);
684 coin_control.m_feerate.reset(); // Explicitly use only fee estimation rate for smart fee labels
685 FeeCalculation feeCalc;
686 CFeeRate feeRate = CFeeRate(GetMinimumFee(1000, coin_control, ::mempool, ::feeEstimator, &feeCalc));
688 ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), feeRate.GetFeePerK()) + "/kB");
690 if (feeCalc.reason == FeeReason::FALLBACK) {
691 ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...)
692 ui->labelFeeEstimation->setText("");
693 ui->fallbackFeeWarningLabel->setVisible(true);
694 int lightness = ui->fallbackFeeWarningLabel->palette().color(QPalette::WindowText).lightness();
695 QColor warning_colour(255 - (lightness / 5), 176 - (lightness / 3), 48 - (lightness / 14));
696 ui->fallbackFeeWarningLabel->setStyleSheet("QLabel { color: " + warning_colour.name() + "; }");
697 ui->fallbackFeeWarningLabel->setIndent(QFontMetrics(ui->fallbackFeeWarningLabel->font()).width("x"));
699 else
701 ui->labelSmartFee2->hide();
702 ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", feeCalc.returnedTarget));
703 ui->fallbackFeeWarningLabel->setVisible(false);
706 updateFeeMinimizedLabel();
709 // Coin Control: copy label "Quantity" to clipboard
710 void SendCoinsDialog::coinControlClipboardQuantity()
712 GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
715 // Coin Control: copy label "Amount" to clipboard
716 void SendCoinsDialog::coinControlClipboardAmount()
718 GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
721 // Coin Control: copy label "Fee" to clipboard
722 void SendCoinsDialog::coinControlClipboardFee()
724 GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
727 // Coin Control: copy label "After fee" to clipboard
728 void SendCoinsDialog::coinControlClipboardAfterFee()
730 GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
733 // Coin Control: copy label "Bytes" to clipboard
734 void SendCoinsDialog::coinControlClipboardBytes()
736 GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, ""));
739 // Coin Control: copy label "Dust" to clipboard
740 void SendCoinsDialog::coinControlClipboardLowOutput()
742 GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text());
745 // Coin Control: copy label "Change" to clipboard
746 void SendCoinsDialog::coinControlClipboardChange()
748 GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
751 // Coin Control: settings menu - coin control enabled/disabled by user
752 void SendCoinsDialog::coinControlFeatureChanged(bool checked)
754 ui->frameCoinControl->setVisible(checked);
756 if (!checked && model) // coin control features disabled
757 CoinControlDialog::coinControl->SetNull();
759 coinControlUpdateLabels();
762 // Coin Control: button inputs -> show actual coin control dialog
763 void SendCoinsDialog::coinControlButtonClicked()
765 CoinControlDialog dlg(platformStyle);
766 dlg.setModel(model);
767 dlg.exec();
768 coinControlUpdateLabels();
771 // Coin Control: checkbox custom change address
772 void SendCoinsDialog::coinControlChangeChecked(int state)
774 if (state == Qt::Unchecked)
776 CoinControlDialog::coinControl->destChange = CNoDestination();
777 ui->labelCoinControlChangeLabel->clear();
779 else
780 // use this to re-validate an already entered address
781 coinControlChangeEdited(ui->lineEditCoinControlChange->text());
783 ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
786 // Coin Control: custom change address changed
787 void SendCoinsDialog::coinControlChangeEdited(const QString& text)
789 if (model && model->getAddressTableModel())
791 // Default to no change address until verified
792 CoinControlDialog::coinControl->destChange = CNoDestination();
793 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}");
795 const CTxDestination dest = DecodeDestination(text.toStdString());
797 if (text.isEmpty()) // Nothing entered
799 ui->labelCoinControlChangeLabel->setText("");
801 else if (!IsValidDestination(dest)) // Invalid address
803 ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Bitcoin address"));
805 else // Valid address
807 if (!model->IsSpendable(dest)) {
808 ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
810 // confirmation dialog
811 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?"),
812 QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
814 if(btnRetVal == QMessageBox::Yes)
815 CoinControlDialog::coinControl->destChange = dest;
816 else
818 ui->lineEditCoinControlChange->setText("");
819 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
820 ui->labelCoinControlChangeLabel->setText("");
823 else // Known change address
825 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
827 // Query label
828 QString associatedLabel = model->getAddressTableModel()->labelForAddress(text);
829 if (!associatedLabel.isEmpty())
830 ui->labelCoinControlChangeLabel->setText(associatedLabel);
831 else
832 ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
834 CoinControlDialog::coinControl->destChange = dest;
840 // Coin Control: update labels
841 void SendCoinsDialog::coinControlUpdateLabels()
843 if (!model || !model->getOptionsModel())
844 return;
846 updateCoinControlState(*CoinControlDialog::coinControl);
848 // set pay amounts
849 CoinControlDialog::payAmounts.clear();
850 CoinControlDialog::fSubtractFeeFromAmount = false;
852 for(int i = 0; i < ui->entries->count(); ++i)
854 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
855 if(entry && !entry->isHidden())
857 SendCoinsRecipient rcp = entry->getValue();
858 CoinControlDialog::payAmounts.append(rcp.amount);
859 if (rcp.fSubtractFeeFromAmount)
860 CoinControlDialog::fSubtractFeeFromAmount = true;
864 if (CoinControlDialog::coinControl->HasSelected())
866 // actual coin control calculation
867 CoinControlDialog::updateLabels(model, this);
869 // show coin control stats
870 ui->labelCoinControlAutomaticallySelected->hide();
871 ui->widgetCoinControl->show();
873 else
875 // hide coin control stats
876 ui->labelCoinControlAutomaticallySelected->show();
877 ui->widgetCoinControl->hide();
878 ui->labelCoinControlInsuffFunds->hide();
882 SendConfirmationDialog::SendConfirmationDialog(const QString &title, const QString &text, int _secDelay,
883 QWidget *parent) :
884 QMessageBox(QMessageBox::Question, title, text, QMessageBox::Yes | QMessageBox::Cancel, parent), secDelay(_secDelay)
886 setDefaultButton(QMessageBox::Cancel);
887 yesButton = button(QMessageBox::Yes);
888 updateYesButton();
889 connect(&countDownTimer, SIGNAL(timeout()), this, SLOT(countDown()));
892 int SendConfirmationDialog::exec()
894 updateYesButton();
895 countDownTimer.start(1000);
896 return QMessageBox::exec();
899 void SendConfirmationDialog::countDown()
901 secDelay--;
902 updateYesButton();
904 if(secDelay <= 0)
906 countDownTimer.stop();
910 void SendConfirmationDialog::updateYesButton()
912 if(secDelay > 0)
914 yesButton->setEnabled(false);
915 yesButton->setText(tr("Yes") + " (" + QString::number(secDelay) + ")");
917 else
919 yesButton->setEnabled(true);
920 yesButton->setText(tr("Yes"));