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"
13 #include "optionsmodel.h"
14 #include "platformstyle.h"
15 #include "sendcoinsentry.h"
16 #include "walletmodel.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>
31 #include <QTextDocument>
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();
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
) {
50 return confTargets
.size() - 1;
53 SendCoinsDialog::SendCoinsDialog(const PlatformStyle
*_platformStyle
, QWidget
*parent
) :
55 ui(new Ui::SendCoinsDialog
),
58 fNewRecipientAllowed(true),
60 platformStyle(_platformStyle
)
64 if (!_platformStyle
->getImagesOnButtons()) {
65 ui
->addButton
->setIcon(QIcon());
66 ui
->clearButton
->setIcon(QIcon());
67 ui
->sendButton
->setIcon(QIcon());
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);
78 connect(ui
->addButton
, SIGNAL(clicked()), this, SLOT(addEntry()));
79 connect(ui
->clearButton
, SIGNAL(clicked()), this, SLOT(clear()));
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
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
;
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());
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()));
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();
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();
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)
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()));
208 ui
->confTargetSelector
->setCurrentIndex(getIndexForConfTarget(settings
.value("nConfTarget").toInt()));
212 SendCoinsDialog::~SendCoinsDialog()
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());
225 void SendCoinsDialog::on_sendButton_clicked()
227 if(!model
|| !model
->getOptionsModel())
230 QList
<SendCoinsRecipient
> recipients
;
233 for(int i
= 0; i
< ui
->entries
->count(); ++i
)
235 SendCoinsEntry
*entry
= qobject_cast
<SendCoinsEntry
*>(ui
->entries
->itemAt(i
)->widget());
238 if(entry
->validate())
240 recipients
.append(entry
->getValue());
249 if(!valid
|| recipients
.isEmpty())
254 fNewRecipientAllowed
= false;
255 WalletModel::UnlockContext
ctx(model
->requestUnlock());
258 // Unlock wallet was cancelled
259 fNewRecipientAllowed
= true;
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
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;
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
));
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");
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;
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
)
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();
394 updateTabsAndLabels();
397 void SendCoinsDialog::reject()
402 void SendCoinsDialog::accept()
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
419 ui
->scrollAreaWidgetContents
->resize(ui
->scrollAreaWidgetContents
->sizeHint());
420 qApp
->processEvents();
421 QScrollBar
* bar
= ui
->scrollArea
->verticalScrollBar();
423 bar
->setSliderPosition(bar
->maximum());
425 updateTabsAndLabels();
429 void SendCoinsDialog::updateTabsAndLabels()
432 coinControlUpdateLabels();
435 void SendCoinsDialog::removeEntry(SendCoinsEntry
* entry
)
439 // If the last entry is about to be removed add an empty one
440 if (ui
->entries
->count() == 1)
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());
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());
481 entry
->setAddress(address
);
484 void SendCoinsDialog::pasteEntry(const SendCoinsRecipient
&rv
)
486 if(!fNewRecipientAllowed
)
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());
505 updateTabsAndLabels();
508 bool SendCoinsDialog::handlePaymentRequest(const SendCoinsRecipient
&rv
)
510 // Just paste the entry, all pre-checks
511 // are done in paymentserver.cpp.
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());
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.");
553 case WalletModel::InvalidAmount
:
554 msgParams
.first
= tr("The amount to pay must be larger than 0.");
556 case WalletModel::AmountExceedsBalance
:
557 msgParams
.first
= tr("The amount exceeds your balance.");
559 case WalletModel::AmountWithFeeExceedsBalance
:
560 msgParams
.first
= tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg
);
562 case WalletModel::DuplicateAddress
:
563 msgParams
.first
= tr("Duplicate address found: addresses should only be used once each.");
565 case WalletModel::TransactionCreationFailed
:
566 msgParams
.first
= tr("Transaction creation failed!");
567 msgParams
.second
= CClientUIInterface::MSG_ERROR
;
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
;
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
));
576 case WalletModel::PaymentRequestExpired
:
577 msgParams
.first
= tr("Payment request expired.");
578 msgParams
.second
= CClientUIInterface::MSG_ERROR
;
580 // included to prevent a compiler warning.
581 case WalletModel::OK
:
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())
634 if (ui
->radioSmartFee
->isChecked())
635 ui
->labelFeeMinimized
->setText(ui
->labelSmartFee
->text());
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());
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())
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"));
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
);
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();
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
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();
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;}");
816 QString associatedLabel
= model
->getAddressTableModel()->labelForAddress(text
);
817 if (!associatedLabel
.isEmpty())
818 ui
->labelCoinControlChangeLabel
->setText(associatedLabel
);
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())
834 updateCoinControlState(*CoinControlDialog::coinControl
);
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();
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
,
872 QMessageBox(QMessageBox::Question
, title
, text
, QMessageBox::Yes
| QMessageBox::Cancel
, parent
), secDelay(_secDelay
)
874 setDefaultButton(QMessageBox::Cancel
);
875 yesButton
= button(QMessageBox::Yes
);
877 connect(&countDownTimer
, SIGNAL(timeout()), this, SLOT(countDown()));
880 int SendConfirmationDialog::exec()
883 countDownTimer
.start(1000);
884 return QMessageBox::exec();
887 void SendConfirmationDialog::countDown()
894 countDownTimer
.stop();
898 void SendConfirmationDialog::updateYesButton()
902 yesButton
->setEnabled(false);
903 yesButton
->setText(tr("Yes") + " (" + QString::number(secDelay
) + ")");
907 yesButton
->setEnabled(true);
908 yesButton
->setText(tr("Yes"));