Merge #10574: Remove includes in .cpp files for things the corresponding .h file...
[bitcoinplatinum.git] / src / qt / sendcoinsdialog.cpp
blob2a46cdab8c6cb15751a0a7a881208af66a21670f
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 <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(model->getDefaultWalletRbf() ? Qt::Checked : Qt::Unchecked);
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 />(=%2)</span>")
340 .arg(alternativeUnits.join(" " + tr("or") + "<br />")));
342 if (ui->optInRBF->isChecked())
344 questionString.append("<hr /><span>");
345 questionString.append(tr("You can increase the fee later (signals Replace-By-Fee)."));
346 questionString.append("</span>");
349 SendConfirmationDialog confirmationDialog(tr("Confirm send coins"),
350 questionString.arg(formatted.join("<br />")), SEND_CONFIRM_DELAY, this);
351 confirmationDialog.exec();
352 QMessageBox::StandardButton retval = (QMessageBox::StandardButton)confirmationDialog.result();
354 if(retval != QMessageBox::Yes)
356 fNewRecipientAllowed = true;
357 return;
360 // now send the prepared transaction
361 WalletModel::SendCoinsReturn sendStatus = model->sendCoins(currentTransaction);
362 // process sendStatus and on error generate message shown to user
363 processSendCoinsReturn(sendStatus);
365 if (sendStatus.status == WalletModel::OK)
367 accept();
368 CoinControlDialog::coinControl->UnSelectAll();
369 coinControlUpdateLabels();
371 fNewRecipientAllowed = true;
374 void SendCoinsDialog::clear()
376 // Remove entries until only one left
377 while(ui->entries->count())
379 ui->entries->takeAt(0)->widget()->deleteLater();
381 addEntry();
383 updateTabsAndLabels();
386 void SendCoinsDialog::reject()
388 clear();
391 void SendCoinsDialog::accept()
393 clear();
396 SendCoinsEntry *SendCoinsDialog::addEntry()
398 SendCoinsEntry *entry = new SendCoinsEntry(platformStyle, this);
399 entry->setModel(model);
400 ui->entries->addWidget(entry);
401 connect(entry, SIGNAL(removeEntry(SendCoinsEntry*)), this, SLOT(removeEntry(SendCoinsEntry*)));
402 connect(entry, SIGNAL(useAvailableBalance(SendCoinsEntry*)), this, SLOT(useAvailableBalance(SendCoinsEntry*)));
403 connect(entry, SIGNAL(payAmountChanged()), this, SLOT(coinControlUpdateLabels()));
404 connect(entry, SIGNAL(subtractFeeFromAmountChanged()), this, SLOT(coinControlUpdateLabels()));
406 // Focus the field, so that entry can start immediately
407 entry->clear();
408 entry->setFocus();
409 ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
410 qApp->processEvents();
411 QScrollBar* bar = ui->scrollArea->verticalScrollBar();
412 if(bar)
413 bar->setSliderPosition(bar->maximum());
415 updateTabsAndLabels();
416 return entry;
419 void SendCoinsDialog::updateTabsAndLabels()
421 setupTabChain(0);
422 coinControlUpdateLabels();
425 void SendCoinsDialog::removeEntry(SendCoinsEntry* entry)
427 entry->hide();
429 // If the last entry is about to be removed add an empty one
430 if (ui->entries->count() == 1)
431 addEntry();
433 entry->deleteLater();
435 updateTabsAndLabels();
438 QWidget *SendCoinsDialog::setupTabChain(QWidget *prev)
440 for(int i = 0; i < ui->entries->count(); ++i)
442 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
443 if(entry)
445 prev = entry->setupTabChain(prev);
448 QWidget::setTabOrder(prev, ui->sendButton);
449 QWidget::setTabOrder(ui->sendButton, ui->clearButton);
450 QWidget::setTabOrder(ui->clearButton, ui->addButton);
451 return ui->addButton;
454 void SendCoinsDialog::setAddress(const QString &address)
456 SendCoinsEntry *entry = 0;
457 // Replace the first entry if it is still unused
458 if(ui->entries->count() == 1)
460 SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
461 if(first->isClear())
463 entry = first;
466 if(!entry)
468 entry = addEntry();
471 entry->setAddress(address);
474 void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv)
476 if(!fNewRecipientAllowed)
477 return;
479 SendCoinsEntry *entry = 0;
480 // Replace the first entry if it is still unused
481 if(ui->entries->count() == 1)
483 SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
484 if(first->isClear())
486 entry = first;
489 if(!entry)
491 entry = addEntry();
494 entry->setValue(rv);
495 updateTabsAndLabels();
498 bool SendCoinsDialog::handlePaymentRequest(const SendCoinsRecipient &rv)
500 // Just paste the entry, all pre-checks
501 // are done in paymentserver.cpp.
502 pasteEntry(rv);
503 return true;
506 void SendCoinsDialog::setBalance(const CAmount& balance, const CAmount& unconfirmedBalance, const CAmount& immatureBalance,
507 const CAmount& watchBalance, const CAmount& watchUnconfirmedBalance, const CAmount& watchImmatureBalance)
509 Q_UNUSED(unconfirmedBalance);
510 Q_UNUSED(immatureBalance);
511 Q_UNUSED(watchBalance);
512 Q_UNUSED(watchUnconfirmedBalance);
513 Q_UNUSED(watchImmatureBalance);
515 if(model && model->getOptionsModel())
517 ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), balance));
521 void SendCoinsDialog::updateDisplayUnit()
523 setBalance(model->getBalance(), 0, 0, 0, 0, 0);
524 ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
525 updateMinFeeLabel();
526 updateSmartFeeLabel();
529 void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg)
531 QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
532 // Default to a warning message, override if error message is needed
533 msgParams.second = CClientUIInterface::MSG_WARNING;
535 // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn.
536 // WalletModel::TransactionCommitFailed is used only in WalletModel::sendCoins()
537 // all others are used only in WalletModel::prepareTransaction()
538 switch(sendCoinsReturn.status)
540 case WalletModel::InvalidAddress:
541 msgParams.first = tr("The recipient address is not valid. Please recheck.");
542 break;
543 case WalletModel::InvalidAmount:
544 msgParams.first = tr("The amount to pay must be larger than 0.");
545 break;
546 case WalletModel::AmountExceedsBalance:
547 msgParams.first = tr("The amount exceeds your balance.");
548 break;
549 case WalletModel::AmountWithFeeExceedsBalance:
550 msgParams.first = tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg);
551 break;
552 case WalletModel::DuplicateAddress:
553 msgParams.first = tr("Duplicate address found: addresses should only be used once each.");
554 break;
555 case WalletModel::TransactionCreationFailed:
556 msgParams.first = tr("Transaction creation failed!");
557 msgParams.second = CClientUIInterface::MSG_ERROR;
558 break;
559 case WalletModel::TransactionCommitFailed:
560 msgParams.first = tr("The transaction was rejected with the following reason: %1").arg(sendCoinsReturn.reasonCommitFailed);
561 msgParams.second = CClientUIInterface::MSG_ERROR;
562 break;
563 case WalletModel::AbsurdFee:
564 msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), maxTxFee));
565 break;
566 case WalletModel::PaymentRequestExpired:
567 msgParams.first = tr("Payment request expired.");
568 msgParams.second = CClientUIInterface::MSG_ERROR;
569 break;
570 // included to prevent a compiler warning.
571 case WalletModel::OK:
572 default:
573 return;
576 Q_EMIT message(tr("Send Coins"), msgParams.first, msgParams.second);
579 void SendCoinsDialog::minimizeFeeSection(bool fMinimize)
581 ui->labelFeeMinimized->setVisible(fMinimize);
582 ui->buttonChooseFee ->setVisible(fMinimize);
583 ui->buttonMinimizeFee->setVisible(!fMinimize);
584 ui->frameFeeSelection->setVisible(!fMinimize);
585 ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0);
586 fFeeMinimized = fMinimize;
589 void SendCoinsDialog::on_buttonChooseFee_clicked()
591 minimizeFeeSection(false);
594 void SendCoinsDialog::on_buttonMinimizeFee_clicked()
596 updateFeeMinimizedLabel();
597 minimizeFeeSection(true);
600 void SendCoinsDialog::useAvailableBalance(SendCoinsEntry* entry)
602 // Get CCoinControl instance if CoinControl is enabled or create a new one.
603 CCoinControl coin_control;
604 if (model->getOptionsModel()->getCoinControlFeatures()) {
605 coin_control = *CoinControlDialog::coinControl;
608 // Calculate available amount to send.
609 CAmount amount = model->getBalance(&coin_control);
610 for (int i = 0; i < ui->entries->count(); ++i) {
611 SendCoinsEntry* e = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
612 if (e && !e->isHidden() && e != entry) {
613 amount -= e->getValue().amount;
617 if (amount > 0) {
618 entry->checkSubtractFeeFromAmount();
619 entry->setAmount(amount);
620 } else {
621 entry->setAmount(0);
625 void SendCoinsDialog::setMinimumFee()
627 ui->customFee->setValue(GetRequiredFee(1000));
630 void SendCoinsDialog::updateFeeSectionControls()
632 ui->confTargetSelector ->setEnabled(ui->radioSmartFee->isChecked());
633 ui->labelSmartFee ->setEnabled(ui->radioSmartFee->isChecked());
634 ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked());
635 ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked());
636 ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked());
637 ui->checkBoxMinimumFee ->setEnabled(ui->radioCustomFee->isChecked());
638 ui->labelMinFeeWarning ->setEnabled(ui->radioCustomFee->isChecked());
639 ui->labelCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked());
640 ui->customFee ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked());
643 void SendCoinsDialog::updateFeeMinimizedLabel()
645 if(!model || !model->getOptionsModel())
646 return;
648 if (ui->radioSmartFee->isChecked())
649 ui->labelFeeMinimized->setText(ui->labelSmartFee->text());
650 else {
651 ui->labelFeeMinimized->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), ui->customFee->value()) + "/kB");
655 void SendCoinsDialog::updateMinFeeLabel()
657 if (model && model->getOptionsModel())
658 ui->checkBoxMinimumFee->setText(tr("Pay only the required fee of %1").arg(
659 BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), GetRequiredFee(1000)) + "/kB")
663 void SendCoinsDialog::updateCoinControlState(CCoinControl& ctrl)
665 if (ui->radioCustomFee->isChecked()) {
666 ctrl.m_feerate = CFeeRate(ui->customFee->value());
667 } else {
668 ctrl.m_feerate.reset();
670 // Avoid using global defaults when sending money from the GUI
671 // Either custom fee will be used or if not selected, the confirmation target from dropdown box
672 ctrl.m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
673 ctrl.signalRbf = ui->optInRBF->isChecked();
676 void SendCoinsDialog::updateSmartFeeLabel()
678 if(!model || !model->getOptionsModel())
679 return;
680 CCoinControl coin_control;
681 updateCoinControlState(coin_control);
682 coin_control.m_feerate.reset(); // Explicitly use only fee estimation rate for smart fee labels
683 FeeCalculation feeCalc;
684 CFeeRate feeRate = CFeeRate(GetMinimumFee(1000, coin_control, ::mempool, ::feeEstimator, &feeCalc));
686 ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), feeRate.GetFeePerK()) + "/kB");
688 if (feeCalc.reason == FeeReason::FALLBACK) {
689 ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...)
690 ui->labelFeeEstimation->setText("");
691 ui->fallbackFeeWarningLabel->setVisible(true);
692 int lightness = ui->fallbackFeeWarningLabel->palette().color(QPalette::WindowText).lightness();
693 QColor warning_colour(255 - (lightness / 5), 176 - (lightness / 3), 48 - (lightness / 14));
694 ui->fallbackFeeWarningLabel->setStyleSheet("QLabel { color: " + warning_colour.name() + "; }");
695 ui->fallbackFeeWarningLabel->setIndent(QFontMetrics(ui->fallbackFeeWarningLabel->font()).width("x"));
697 else
699 ui->labelSmartFee2->hide();
700 ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", feeCalc.returnedTarget));
701 ui->fallbackFeeWarningLabel->setVisible(false);
704 updateFeeMinimizedLabel();
707 // Coin Control: copy label "Quantity" to clipboard
708 void SendCoinsDialog::coinControlClipboardQuantity()
710 GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
713 // Coin Control: copy label "Amount" to clipboard
714 void SendCoinsDialog::coinControlClipboardAmount()
716 GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
719 // Coin Control: copy label "Fee" to clipboard
720 void SendCoinsDialog::coinControlClipboardFee()
722 GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
725 // Coin Control: copy label "After fee" to clipboard
726 void SendCoinsDialog::coinControlClipboardAfterFee()
728 GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
731 // Coin Control: copy label "Bytes" to clipboard
732 void SendCoinsDialog::coinControlClipboardBytes()
734 GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, ""));
737 // Coin Control: copy label "Dust" to clipboard
738 void SendCoinsDialog::coinControlClipboardLowOutput()
740 GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text());
743 // Coin Control: copy label "Change" to clipboard
744 void SendCoinsDialog::coinControlClipboardChange()
746 GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
749 // Coin Control: settings menu - coin control enabled/disabled by user
750 void SendCoinsDialog::coinControlFeatureChanged(bool checked)
752 ui->frameCoinControl->setVisible(checked);
754 if (!checked && model) // coin control features disabled
755 CoinControlDialog::coinControl->SetNull();
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 const CTxDestination dest = DecodeDestination(text.toStdString());
795 if (text.isEmpty()) // Nothing entered
797 ui->labelCoinControlChangeLabel->setText("");
799 else if (!IsValidDestination(dest)) // Invalid address
801 ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Bitcoin address"));
803 else // Valid address
805 if (!model->IsSpendable(dest)) {
806 ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
808 // confirmation dialog
809 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?"),
810 QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
812 if(btnRetVal == QMessageBox::Yes)
813 CoinControlDialog::coinControl->destChange = dest;
814 else
816 ui->lineEditCoinControlChange->setText("");
817 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
818 ui->labelCoinControlChangeLabel->setText("");
821 else // Known change address
823 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
825 // Query label
826 QString associatedLabel = model->getAddressTableModel()->labelForAddress(text);
827 if (!associatedLabel.isEmpty())
828 ui->labelCoinControlChangeLabel->setText(associatedLabel);
829 else
830 ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
832 CoinControlDialog::coinControl->destChange = dest;
838 // Coin Control: update labels
839 void SendCoinsDialog::coinControlUpdateLabels()
841 if (!model || !model->getOptionsModel())
842 return;
844 updateCoinControlState(*CoinControlDialog::coinControl);
846 // set pay amounts
847 CoinControlDialog::payAmounts.clear();
848 CoinControlDialog::fSubtractFeeFromAmount = false;
850 for(int i = 0; i < ui->entries->count(); ++i)
852 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
853 if(entry && !entry->isHidden())
855 SendCoinsRecipient rcp = entry->getValue();
856 CoinControlDialog::payAmounts.append(rcp.amount);
857 if (rcp.fSubtractFeeFromAmount)
858 CoinControlDialog::fSubtractFeeFromAmount = true;
862 if (CoinControlDialog::coinControl->HasSelected())
864 // actual coin control calculation
865 CoinControlDialog::updateLabels(model, this);
867 // show coin control stats
868 ui->labelCoinControlAutomaticallySelected->hide();
869 ui->widgetCoinControl->show();
871 else
873 // hide coin control stats
874 ui->labelCoinControlAutomaticallySelected->show();
875 ui->widgetCoinControl->hide();
876 ui->labelCoinControlInsuffFunds->hide();
880 SendConfirmationDialog::SendConfirmationDialog(const QString &title, const QString &text, int _secDelay,
881 QWidget *parent) :
882 QMessageBox(QMessageBox::Question, title, text, QMessageBox::Yes | QMessageBox::Cancel, parent), secDelay(_secDelay)
884 setDefaultButton(QMessageBox::Cancel);
885 yesButton = button(QMessageBox::Yes);
886 updateYesButton();
887 connect(&countDownTimer, SIGNAL(timeout()), this, SLOT(countDown()));
890 int SendConfirmationDialog::exec()
892 updateYesButton();
893 countDownTimer.start(1000);
894 return QMessageBox::exec();
897 void SendConfirmationDialog::countDown()
899 secDelay--;
900 updateYesButton();
902 if(secDelay <= 0)
904 countDownTimer.stop();
908 void SendConfirmationDialog::updateYesButton()
910 if(secDelay > 0)
912 yesButton->setEnabled(false);
913 yesButton->setText(tr("Yes") + " (" + QString::number(secDelay) + ")");
915 else
917 yesButton->setEnabled(true);
918 yesButton->setText(tr("Yes"));