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>
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>
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();
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
) {
47 return confTargets
.size() - 1;
50 SendCoinsDialog::SendCoinsDialog(const PlatformStyle
*_platformStyle
, QWidget
*parent
) :
52 ui(new Ui::SendCoinsDialog
),
55 fNewRecipientAllowed(true),
57 platformStyle(_platformStyle
)
61 if (!_platformStyle
->getImagesOnButtons()) {
62 ui
->addButton
->setIcon(QIcon());
63 ui
->clearButton
->setIcon(QIcon());
64 ui
->sendButton
->setIcon(QIcon());
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);
75 connect(ui
->addButton
, SIGNAL(clicked()), this, SLOT(addEntry()));
76 connect(ui
->clearButton
, SIGNAL(clicked()), this, SLOT(clear()));
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
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
;
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());
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()));
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();
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();
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)
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()));
198 ui
->confTargetSelector
->setCurrentIndex(getIndexForConfTarget(settings
.value("nConfTarget").toInt()));
202 SendCoinsDialog::~SendCoinsDialog()
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());
214 void SendCoinsDialog::on_sendButton_clicked()
216 if(!model
|| !model
->getOptionsModel())
219 QList
<SendCoinsRecipient
> recipients
;
222 for(int i
= 0; i
< ui
->entries
->count(); ++i
)
224 SendCoinsEntry
*entry
= qobject_cast
<SendCoinsEntry
*>(ui
->entries
->itemAt(i
)->widget());
227 if(entry
->validate())
229 recipients
.append(entry
->getValue());
238 if(!valid
|| recipients
.isEmpty())
243 fNewRecipientAllowed
= false;
244 WalletModel::UnlockContext
ctx(model
->requestUnlock());
247 // Unlock wallet was cancelled
248 fNewRecipientAllowed
= true;
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
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;
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
));
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");
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)."));
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;
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
)
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();
385 updateTabsAndLabels();
388 void SendCoinsDialog::reject()
393 void SendCoinsDialog::accept()
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
411 ui
->scrollAreaWidgetContents
->resize(ui
->scrollAreaWidgetContents
->sizeHint());
412 qApp
->processEvents();
413 QScrollBar
* bar
= ui
->scrollArea
->verticalScrollBar();
415 bar
->setSliderPosition(bar
->maximum());
417 updateTabsAndLabels();
421 void SendCoinsDialog::updateTabsAndLabels()
424 coinControlUpdateLabels();
427 void SendCoinsDialog::removeEntry(SendCoinsEntry
* entry
)
431 // If the last entry is about to be removed add an empty one
432 if (ui
->entries
->count() == 1)
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());
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());
473 entry
->setAddress(address
);
476 void SendCoinsDialog::pasteEntry(const SendCoinsRecipient
&rv
)
478 if(!fNewRecipientAllowed
)
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());
497 updateTabsAndLabels();
500 bool SendCoinsDialog::handlePaymentRequest(const SendCoinsRecipient
&rv
)
502 // Just paste the entry, all pre-checks
503 // are done in paymentserver.cpp.
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());
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.");
545 case WalletModel::InvalidAmount
:
546 msgParams
.first
= tr("The amount to pay must be larger than 0.");
548 case WalletModel::AmountExceedsBalance
:
549 msgParams
.first
= tr("The amount exceeds your balance.");
551 case WalletModel::AmountWithFeeExceedsBalance
:
552 msgParams
.first
= tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg
);
554 case WalletModel::DuplicateAddress
:
555 msgParams
.first
= tr("Duplicate address found: addresses should only be used once each.");
557 case WalletModel::TransactionCreationFailed
:
558 msgParams
.first
= tr("Transaction creation failed!");
559 msgParams
.second
= CClientUIInterface::MSG_ERROR
;
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
;
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
));
568 case WalletModel::PaymentRequestExpired
:
569 msgParams
.first
= tr("Payment request expired.");
570 msgParams
.second
= CClientUIInterface::MSG_ERROR
;
572 // included to prevent a compiler warning.
573 case WalletModel::OK
:
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
;
620 entry
->checkSubtractFeeFromAmount();
621 entry
->setAmount(amount
);
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())
650 if (ui
->radioSmartFee
->isChecked())
651 ui
->labelFeeMinimized
->setText(ui
->labelSmartFee
->text());
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());
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())
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"));
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
);
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();
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
;
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;}");
828 QString associatedLabel
= model
->getAddressTableModel()->labelForAddress(text
);
829 if (!associatedLabel
.isEmpty())
830 ui
->labelCoinControlChangeLabel
->setText(associatedLabel
);
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())
846 updateCoinControlState(*CoinControlDialog::coinControl
);
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();
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
,
884 QMessageBox(QMessageBox::Question
, title
, text
, QMessageBox::Yes
| QMessageBox::Cancel
, parent
), secDelay(_secDelay
)
886 setDefaultButton(QMessageBox::Cancel
);
887 yesButton
= button(QMessageBox::Yes
);
889 connect(&countDownTimer
, SIGNAL(timeout()), this, SLOT(countDown()));
892 int SendConfirmationDialog::exec()
895 countDownTimer
.start(1000);
896 return QMessageBox::exec();
899 void SendConfirmationDialog::countDown()
906 countDownTimer
.stop();
910 void SendConfirmationDialog::updateYesButton()
914 yesButton
->setEnabled(false);
915 yesButton
->setText(tr("Yes") + " (" + QString::number(secDelay
) + ")");
919 yesButton
->setEnabled(true);
920 yesButton
->setText(tr("Yes"));