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>
16 #include <qt/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/fees.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("nSmartFeeSliderPosition"))
118 settings
.setValue("nSmartFeeSliderPosition", 0);
119 if (!settings
.contains("nTransactionFee"))
120 settings
.setValue("nTransactionFee", (qint64
)DEFAULT_TRANSACTION_FEE
);
121 if (!settings
.contains("fPayOnlyMinFee"))
122 settings
.setValue("fPayOnlyMinFee", false);
123 ui
->groupFee
->setId(ui
->radioSmartFee
, 0);
124 ui
->groupFee
->setId(ui
->radioCustomFee
, 1);
125 ui
->groupFee
->button((int)std::max(0, std::min(1, settings
.value("nFeeRadio").toInt())))->setChecked(true);
126 ui
->customFee
->setValue(settings
.value("nTransactionFee").toLongLong());
127 ui
->checkBoxMinimumFee
->setChecked(settings
.value("fPayOnlyMinFee").toBool());
128 minimizeFeeSection(settings
.value("fFeeSectionMinimized").toBool());
131 void SendCoinsDialog::setClientModel(ClientModel
*_clientModel
)
133 this->clientModel
= _clientModel
;
136 connect(_clientModel
, SIGNAL(numBlocksChanged(int,QDateTime
,double,bool)), this, SLOT(updateSmartFeeLabel()));
140 void SendCoinsDialog::setModel(WalletModel
*_model
)
142 this->model
= _model
;
144 if(_model
&& _model
->getOptionsModel())
146 for(int i
= 0; i
< ui
->entries
->count(); ++i
)
148 SendCoinsEntry
*entry
= qobject_cast
<SendCoinsEntry
*>(ui
->entries
->itemAt(i
)->widget());
151 entry
->setModel(_model
);
155 setBalance(_model
->getBalance(), _model
->getUnconfirmedBalance(), _model
->getImmatureBalance(),
156 _model
->getWatchBalance(), _model
->getWatchUnconfirmedBalance(), _model
->getWatchImmatureBalance());
157 connect(_model
, SIGNAL(balanceChanged(CAmount
,CAmount
,CAmount
,CAmount
,CAmount
,CAmount
)), this, SLOT(setBalance(CAmount
,CAmount
,CAmount
,CAmount
,CAmount
,CAmount
)));
158 connect(_model
->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
162 connect(_model
->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(coinControlUpdateLabels()));
163 connect(_model
->getOptionsModel(), SIGNAL(coinControlFeaturesChanged(bool)), this, SLOT(coinControlFeatureChanged(bool)));
164 ui
->frameCoinControl
->setVisible(_model
->getOptionsModel()->getCoinControlFeatures());
165 coinControlUpdateLabels();
168 for (const int &n
: confTargets
) {
169 ui
->confTargetSelector
->addItem(tr("%1 (%2 blocks)").arg(GUIUtil::formatNiceTimeOffset(n
*Params().GetConsensus().nPowTargetSpacing
)).arg(n
));
171 connect(ui
->confTargetSelector
, SIGNAL(currentIndexChanged(int)), this, SLOT(updateSmartFeeLabel()));
172 connect(ui
->confTargetSelector
, SIGNAL(currentIndexChanged(int)), this, SLOT(coinControlUpdateLabels()));
173 connect(ui
->groupFee
, SIGNAL(buttonClicked(int)), this, SLOT(updateFeeSectionControls()));
174 connect(ui
->groupFee
, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels()));
175 connect(ui
->customFee
, SIGNAL(valueChanged()), this, SLOT(coinControlUpdateLabels()));
176 connect(ui
->checkBoxMinimumFee
, SIGNAL(stateChanged(int)), this, SLOT(setMinimumFee()));
177 connect(ui
->checkBoxMinimumFee
, SIGNAL(stateChanged(int)), this, SLOT(updateFeeSectionControls()));
178 connect(ui
->checkBoxMinimumFee
, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels()));
179 connect(ui
->optInRBF
, SIGNAL(stateChanged(int)), this, SLOT(updateSmartFeeLabel()));
180 connect(ui
->optInRBF
, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels()));
181 ui
->customFee
->setSingleStep(GetRequiredFee(1000));
182 updateFeeSectionControls();
184 updateSmartFeeLabel();
186 // set default rbf checkbox state
187 ui
->optInRBF
->setCheckState(model
->getDefaultWalletRbf() ? Qt::Checked
: Qt::Unchecked
);
189 // set the smartfee-sliders default value (wallets default conf.target or last stored value)
191 if (settings
.value("nSmartFeeSliderPosition").toInt() != 0) {
192 // migrate nSmartFeeSliderPosition to nConfTarget
193 // nConfTarget is available since 0.15 (replaced nSmartFeeSliderPosition)
194 int nConfirmTarget
= 25 - settings
.value("nSmartFeeSliderPosition").toInt(); // 25 == old slider range
195 settings
.setValue("nConfTarget", nConfirmTarget
);
196 settings
.remove("nSmartFeeSliderPosition");
198 if (settings
.value("nConfTarget").toInt() == 0)
199 ui
->confTargetSelector
->setCurrentIndex(getIndexForConfTarget(model
->getDefaultConfirmTarget()));
201 ui
->confTargetSelector
->setCurrentIndex(getIndexForConfTarget(settings
.value("nConfTarget").toInt()));
205 SendCoinsDialog::~SendCoinsDialog()
208 settings
.setValue("fFeeSectionMinimized", fFeeMinimized
);
209 settings
.setValue("nFeeRadio", ui
->groupFee
->checkedId());
210 settings
.setValue("nConfTarget", getConfTargetForIndex(ui
->confTargetSelector
->currentIndex()));
211 settings
.setValue("nTransactionFee", (qint64
)ui
->customFee
->value());
212 settings
.setValue("fPayOnlyMinFee", ui
->checkBoxMinimumFee
->isChecked());
217 void SendCoinsDialog::on_sendButton_clicked()
219 if(!model
|| !model
->getOptionsModel())
222 QList
<SendCoinsRecipient
> recipients
;
225 for(int i
= 0; i
< ui
->entries
->count(); ++i
)
227 SendCoinsEntry
*entry
= qobject_cast
<SendCoinsEntry
*>(ui
->entries
->itemAt(i
)->widget());
230 if(entry
->validate())
232 recipients
.append(entry
->getValue());
241 if(!valid
|| recipients
.isEmpty())
246 fNewRecipientAllowed
= false;
247 WalletModel::UnlockContext
ctx(model
->requestUnlock());
250 // Unlock wallet was cancelled
251 fNewRecipientAllowed
= true;
255 // prepare transaction for getting txFee earlier
256 WalletModelTransaction
currentTransaction(recipients
);
257 WalletModel::SendCoinsReturn prepareStatus
;
259 // Always use a CCoinControl instance, use the CoinControlDialog instance if CoinControl has been enabled
261 if (model
->getOptionsModel()->getCoinControlFeatures())
262 ctrl
= *CoinControlDialog::coinControl
;
264 updateCoinControlState(ctrl
);
266 prepareStatus
= model
->prepareTransaction(currentTransaction
, ctrl
);
268 // process prepareStatus and on error generate message shown to user
269 processSendCoinsReturn(prepareStatus
,
270 BitcoinUnits::formatWithUnit(model
->getOptionsModel()->getDisplayUnit(), currentTransaction
.getTransactionFee()));
272 if(prepareStatus
.status
!= WalletModel::OK
) {
273 fNewRecipientAllowed
= true;
277 CAmount txFee
= currentTransaction
.getTransactionFee();
279 // Format confirmation message
280 QStringList formatted
;
281 for (const SendCoinsRecipient
&rcp
: currentTransaction
.getRecipients())
283 // generate bold amount string
284 QString amount
= "<b>" + BitcoinUnits::formatHtmlWithUnit(model
->getOptionsModel()->getDisplayUnit(), rcp
.amount
);
285 amount
.append("</b>");
286 // generate monospace address string
287 QString address
= "<span style='font-family: monospace;'>" + rcp
.address
;
288 address
.append("</span>");
290 QString recipientElement
;
292 if (!rcp
.paymentRequest
.IsInitialized()) // normal payment
294 if(rcp
.label
.length() > 0) // label with address
296 recipientElement
= tr("%1 to %2").arg(amount
, GUIUtil::HtmlEscape(rcp
.label
));
297 recipientElement
.append(QString(" (%1)").arg(address
));
301 recipientElement
= tr("%1 to %2").arg(amount
, address
);
304 else if(!rcp
.authenticatedMerchant
.isEmpty()) // authenticated payment request
306 recipientElement
= tr("%1 to %2").arg(amount
, GUIUtil::HtmlEscape(rcp
.authenticatedMerchant
));
308 else // unauthenticated payment request
310 recipientElement
= tr("%1 to %2").arg(amount
, address
);
313 formatted
.append(recipientElement
);
316 QString questionString
= tr("Are you sure you want to send?");
317 questionString
.append("<br /><br />%1");
321 // append fee string if a fee is required
322 questionString
.append("<hr /><span style='color:#aa0000;'>");
323 questionString
.append(BitcoinUnits::formatHtmlWithUnit(model
->getOptionsModel()->getDisplayUnit(), txFee
));
324 questionString
.append("</span> ");
325 questionString
.append(tr("added as transaction fee"));
327 // append transaction size
328 questionString
.append(" (" + QString::number((double)currentTransaction
.getTransactionSize() / 1000) + " kB)");
331 // add total amount in all subdivision units
332 questionString
.append("<hr />");
333 CAmount totalAmount
= currentTransaction
.getTotalTransactionAmount() + txFee
;
334 QStringList alternativeUnits
;
335 for (BitcoinUnits::Unit u
: BitcoinUnits::availableUnits())
337 if(u
!= model
->getOptionsModel()->getDisplayUnit())
338 alternativeUnits
.append(BitcoinUnits::formatHtmlWithUnit(u
, totalAmount
));
340 questionString
.append(tr("Total Amount %1")
341 .arg(BitcoinUnits::formatHtmlWithUnit(model
->getOptionsModel()->getDisplayUnit(), totalAmount
)));
342 questionString
.append(QString("<span style='font-size:10pt;font-weight:normal;'><br />(=%2)</span>")
343 .arg(alternativeUnits
.join(" " + tr("or") + "<br />")));
345 if (ui
->optInRBF
->isChecked())
347 questionString
.append("<hr /><span>");
348 questionString
.append(tr("This transaction signals replaceability (optin-RBF)."));
349 questionString
.append("</span>");
352 SendConfirmationDialog
confirmationDialog(tr("Confirm send coins"),
353 questionString
.arg(formatted
.join("<br />")), SEND_CONFIRM_DELAY
, this);
354 confirmationDialog
.exec();
355 QMessageBox::StandardButton retval
= (QMessageBox::StandardButton
)confirmationDialog
.result();
357 if(retval
!= QMessageBox::Yes
)
359 fNewRecipientAllowed
= true;
363 // now send the prepared transaction
364 WalletModel::SendCoinsReturn sendStatus
= model
->sendCoins(currentTransaction
);
365 // process sendStatus and on error generate message shown to user
366 processSendCoinsReturn(sendStatus
);
368 if (sendStatus
.status
== WalletModel::OK
)
371 CoinControlDialog::coinControl
->UnSelectAll();
372 coinControlUpdateLabels();
374 fNewRecipientAllowed
= true;
377 void SendCoinsDialog::clear()
379 // Remove entries until only one left
380 while(ui
->entries
->count())
382 ui
->entries
->takeAt(0)->widget()->deleteLater();
386 updateTabsAndLabels();
389 void SendCoinsDialog::reject()
394 void SendCoinsDialog::accept()
399 SendCoinsEntry
*SendCoinsDialog::addEntry()
401 SendCoinsEntry
*entry
= new SendCoinsEntry(platformStyle
, this);
402 entry
->setModel(model
);
403 ui
->entries
->addWidget(entry
);
404 connect(entry
, SIGNAL(removeEntry(SendCoinsEntry
*)), this, SLOT(removeEntry(SendCoinsEntry
*)));
405 connect(entry
, SIGNAL(useAvailableBalance(SendCoinsEntry
*)), this, SLOT(useAvailableBalance(SendCoinsEntry
*)));
406 connect(entry
, SIGNAL(payAmountChanged()), this, SLOT(coinControlUpdateLabels()));
407 connect(entry
, SIGNAL(subtractFeeFromAmountChanged()), this, SLOT(coinControlUpdateLabels()));
409 // Focus the field, so that entry can start immediately
412 ui
->scrollAreaWidgetContents
->resize(ui
->scrollAreaWidgetContents
->sizeHint());
413 qApp
->processEvents();
414 QScrollBar
* bar
= ui
->scrollArea
->verticalScrollBar();
416 bar
->setSliderPosition(bar
->maximum());
418 updateTabsAndLabels();
422 void SendCoinsDialog::updateTabsAndLabels()
425 coinControlUpdateLabels();
428 void SendCoinsDialog::removeEntry(SendCoinsEntry
* entry
)
432 // If the last entry is about to be removed add an empty one
433 if (ui
->entries
->count() == 1)
436 entry
->deleteLater();
438 updateTabsAndLabels();
441 QWidget
*SendCoinsDialog::setupTabChain(QWidget
*prev
)
443 for(int i
= 0; i
< ui
->entries
->count(); ++i
)
445 SendCoinsEntry
*entry
= qobject_cast
<SendCoinsEntry
*>(ui
->entries
->itemAt(i
)->widget());
448 prev
= entry
->setupTabChain(prev
);
451 QWidget::setTabOrder(prev
, ui
->sendButton
);
452 QWidget::setTabOrder(ui
->sendButton
, ui
->clearButton
);
453 QWidget::setTabOrder(ui
->clearButton
, ui
->addButton
);
454 return ui
->addButton
;
457 void SendCoinsDialog::setAddress(const QString
&address
)
459 SendCoinsEntry
*entry
= 0;
460 // Replace the first entry if it is still unused
461 if(ui
->entries
->count() == 1)
463 SendCoinsEntry
*first
= qobject_cast
<SendCoinsEntry
*>(ui
->entries
->itemAt(0)->widget());
474 entry
->setAddress(address
);
477 void SendCoinsDialog::pasteEntry(const SendCoinsRecipient
&rv
)
479 if(!fNewRecipientAllowed
)
482 SendCoinsEntry
*entry
= 0;
483 // Replace the first entry if it is still unused
484 if(ui
->entries
->count() == 1)
486 SendCoinsEntry
*first
= qobject_cast
<SendCoinsEntry
*>(ui
->entries
->itemAt(0)->widget());
498 updateTabsAndLabels();
501 bool SendCoinsDialog::handlePaymentRequest(const SendCoinsRecipient
&rv
)
503 // Just paste the entry, all pre-checks
504 // are done in paymentserver.cpp.
509 void SendCoinsDialog::setBalance(const CAmount
& balance
, const CAmount
& unconfirmedBalance
, const CAmount
& immatureBalance
,
510 const CAmount
& watchBalance
, const CAmount
& watchUnconfirmedBalance
, const CAmount
& watchImmatureBalance
)
512 Q_UNUSED(unconfirmedBalance
);
513 Q_UNUSED(immatureBalance
);
514 Q_UNUSED(watchBalance
);
515 Q_UNUSED(watchUnconfirmedBalance
);
516 Q_UNUSED(watchImmatureBalance
);
518 if(model
&& model
->getOptionsModel())
520 ui
->labelBalance
->setText(BitcoinUnits::formatWithUnit(model
->getOptionsModel()->getDisplayUnit(), balance
));
524 void SendCoinsDialog::updateDisplayUnit()
526 setBalance(model
->getBalance(), 0, 0, 0, 0, 0);
527 ui
->customFee
->setDisplayUnit(model
->getOptionsModel()->getDisplayUnit());
529 updateSmartFeeLabel();
532 void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn
&sendCoinsReturn
, const QString
&msgArg
)
534 QPair
<QString
, CClientUIInterface::MessageBoxFlags
> msgParams
;
535 // Default to a warning message, override if error message is needed
536 msgParams
.second
= CClientUIInterface::MSG_WARNING
;
538 // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn.
539 // WalletModel::TransactionCommitFailed is used only in WalletModel::sendCoins()
540 // all others are used only in WalletModel::prepareTransaction()
541 switch(sendCoinsReturn
.status
)
543 case WalletModel::InvalidAddress
:
544 msgParams
.first
= tr("The recipient address is not valid. Please recheck.");
546 case WalletModel::InvalidAmount
:
547 msgParams
.first
= tr("The amount to pay must be larger than 0.");
549 case WalletModel::AmountExceedsBalance
:
550 msgParams
.first
= tr("The amount exceeds your balance.");
552 case WalletModel::AmountWithFeeExceedsBalance
:
553 msgParams
.first
= tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg
);
555 case WalletModel::DuplicateAddress
:
556 msgParams
.first
= tr("Duplicate address found: addresses should only be used once each.");
558 case WalletModel::TransactionCreationFailed
:
559 msgParams
.first
= tr("Transaction creation failed!");
560 msgParams
.second
= CClientUIInterface::MSG_ERROR
;
562 case WalletModel::TransactionCommitFailed
:
563 msgParams
.first
= tr("The transaction was rejected with the following reason: %1").arg(sendCoinsReturn
.reasonCommitFailed
);
564 msgParams
.second
= CClientUIInterface::MSG_ERROR
;
566 case WalletModel::AbsurdFee
:
567 msgParams
.first
= tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model
->getOptionsModel()->getDisplayUnit(), maxTxFee
));
569 case WalletModel::PaymentRequestExpired
:
570 msgParams
.first
= tr("Payment request expired.");
571 msgParams
.second
= CClientUIInterface::MSG_ERROR
;
573 // included to prevent a compiler warning.
574 case WalletModel::OK
:
579 Q_EMIT
message(tr("Send Coins"), msgParams
.first
, msgParams
.second
);
582 void SendCoinsDialog::minimizeFeeSection(bool fMinimize
)
584 ui
->labelFeeMinimized
->setVisible(fMinimize
);
585 ui
->buttonChooseFee
->setVisible(fMinimize
);
586 ui
->buttonMinimizeFee
->setVisible(!fMinimize
);
587 ui
->frameFeeSelection
->setVisible(!fMinimize
);
588 ui
->horizontalLayoutSmartFee
->setContentsMargins(0, (fMinimize
? 0 : 6), 0, 0);
589 fFeeMinimized
= fMinimize
;
592 void SendCoinsDialog::on_buttonChooseFee_clicked()
594 minimizeFeeSection(false);
597 void SendCoinsDialog::on_buttonMinimizeFee_clicked()
599 updateFeeMinimizedLabel();
600 minimizeFeeSection(true);
603 void SendCoinsDialog::useAvailableBalance(SendCoinsEntry
* entry
)
605 // Get CCoinControl instance if CoinControl is enabled or create a new one.
606 CCoinControl coin_control
;
607 if (model
->getOptionsModel()->getCoinControlFeatures()) {
608 coin_control
= *CoinControlDialog::coinControl
;
611 // Calculate available amount to send.
612 CAmount amount
= model
->getBalance(&coin_control
);
613 for (int i
= 0; i
< ui
->entries
->count(); ++i
) {
614 SendCoinsEntry
* e
= qobject_cast
<SendCoinsEntry
*>(ui
->entries
->itemAt(i
)->widget());
615 if (e
&& !e
->isHidden() && e
!= entry
) {
616 amount
-= e
->getValue().amount
;
621 entry
->checkSubtractFeeFromAmount();
622 entry
->setAmount(amount
);
628 void SendCoinsDialog::setMinimumFee()
630 ui
->customFee
->setValue(GetRequiredFee(1000));
633 void SendCoinsDialog::updateFeeSectionControls()
635 ui
->confTargetSelector
->setEnabled(ui
->radioSmartFee
->isChecked());
636 ui
->labelSmartFee
->setEnabled(ui
->radioSmartFee
->isChecked());
637 ui
->labelSmartFee2
->setEnabled(ui
->radioSmartFee
->isChecked());
638 ui
->labelSmartFee3
->setEnabled(ui
->radioSmartFee
->isChecked());
639 ui
->labelFeeEstimation
->setEnabled(ui
->radioSmartFee
->isChecked());
640 ui
->checkBoxMinimumFee
->setEnabled(ui
->radioCustomFee
->isChecked());
641 ui
->labelMinFeeWarning
->setEnabled(ui
->radioCustomFee
->isChecked());
642 ui
->labelCustomPerKilobyte
->setEnabled(ui
->radioCustomFee
->isChecked() && !ui
->checkBoxMinimumFee
->isChecked());
643 ui
->customFee
->setEnabled(ui
->radioCustomFee
->isChecked() && !ui
->checkBoxMinimumFee
->isChecked());
646 void SendCoinsDialog::updateFeeMinimizedLabel()
648 if(!model
|| !model
->getOptionsModel())
651 if (ui
->radioSmartFee
->isChecked())
652 ui
->labelFeeMinimized
->setText(ui
->labelSmartFee
->text());
654 ui
->labelFeeMinimized
->setText(BitcoinUnits::formatWithUnit(model
->getOptionsModel()->getDisplayUnit(), ui
->customFee
->value()) + "/kB");
658 void SendCoinsDialog::updateMinFeeLabel()
660 if (model
&& model
->getOptionsModel())
661 ui
->checkBoxMinimumFee
->setText(tr("Pay only the required fee of %1").arg(
662 BitcoinUnits::formatWithUnit(model
->getOptionsModel()->getDisplayUnit(), GetRequiredFee(1000)) + "/kB")
666 void SendCoinsDialog::updateCoinControlState(CCoinControl
& ctrl
)
668 if (ui
->radioCustomFee
->isChecked()) {
669 ctrl
.m_feerate
= CFeeRate(ui
->customFee
->value());
671 ctrl
.m_feerate
.reset();
673 // Avoid using global defaults when sending money from the GUI
674 // Either custom fee will be used or if not selected, the confirmation target from dropdown box
675 ctrl
.m_confirm_target
= getConfTargetForIndex(ui
->confTargetSelector
->currentIndex());
676 ctrl
.signalRbf
= ui
->optInRBF
->isChecked();
679 void SendCoinsDialog::updateSmartFeeLabel()
681 if(!model
|| !model
->getOptionsModel())
683 CCoinControl coin_control
;
684 updateCoinControlState(coin_control
);
685 coin_control
.m_feerate
.reset(); // Explicitly use only fee estimation rate for smart fee labels
686 FeeCalculation feeCalc
;
687 CFeeRate feeRate
= CFeeRate(GetMinimumFee(1000, coin_control
, ::mempool
, ::feeEstimator
, &feeCalc
));
689 ui
->labelSmartFee
->setText(BitcoinUnits::formatWithUnit(model
->getOptionsModel()->getDisplayUnit(), feeRate
.GetFeePerK()) + "/kB");
691 if (feeCalc
.reason
== FeeReason::FALLBACK
) {
692 ui
->labelSmartFee2
->show(); // (Smart fee not initialized yet. This usually takes a few blocks...)
693 ui
->labelFeeEstimation
->setText("");
694 ui
->fallbackFeeWarningLabel
->setVisible(true);
695 int lightness
= ui
->fallbackFeeWarningLabel
->palette().color(QPalette::WindowText
).lightness();
696 QColor
warning_colour(255 - (lightness
/ 5), 176 - (lightness
/ 3), 48 - (lightness
/ 14));
697 ui
->fallbackFeeWarningLabel
->setStyleSheet("QLabel { color: " + warning_colour
.name() + "; }");
698 ui
->fallbackFeeWarningLabel
->setIndent(QFontMetrics(ui
->fallbackFeeWarningLabel
->font()).width("x"));
702 ui
->labelSmartFee2
->hide();
703 ui
->labelFeeEstimation
->setText(tr("Estimated to begin confirmation within %n block(s).", "", feeCalc
.returnedTarget
));
704 ui
->fallbackFeeWarningLabel
->setVisible(false);
707 updateFeeMinimizedLabel();
710 // Coin Control: copy label "Quantity" to clipboard
711 void SendCoinsDialog::coinControlClipboardQuantity()
713 GUIUtil::setClipboard(ui
->labelCoinControlQuantity
->text());
716 // Coin Control: copy label "Amount" to clipboard
717 void SendCoinsDialog::coinControlClipboardAmount()
719 GUIUtil::setClipboard(ui
->labelCoinControlAmount
->text().left(ui
->labelCoinControlAmount
->text().indexOf(" ")));
722 // Coin Control: copy label "Fee" to clipboard
723 void SendCoinsDialog::coinControlClipboardFee()
725 GUIUtil::setClipboard(ui
->labelCoinControlFee
->text().left(ui
->labelCoinControlFee
->text().indexOf(" ")).replace(ASYMP_UTF8
, ""));
728 // Coin Control: copy label "After fee" to clipboard
729 void SendCoinsDialog::coinControlClipboardAfterFee()
731 GUIUtil::setClipboard(ui
->labelCoinControlAfterFee
->text().left(ui
->labelCoinControlAfterFee
->text().indexOf(" ")).replace(ASYMP_UTF8
, ""));
734 // Coin Control: copy label "Bytes" to clipboard
735 void SendCoinsDialog::coinControlClipboardBytes()
737 GUIUtil::setClipboard(ui
->labelCoinControlBytes
->text().replace(ASYMP_UTF8
, ""));
740 // Coin Control: copy label "Dust" to clipboard
741 void SendCoinsDialog::coinControlClipboardLowOutput()
743 GUIUtil::setClipboard(ui
->labelCoinControlLowOutput
->text());
746 // Coin Control: copy label "Change" to clipboard
747 void SendCoinsDialog::coinControlClipboardChange()
749 GUIUtil::setClipboard(ui
->labelCoinControlChange
->text().left(ui
->labelCoinControlChange
->text().indexOf(" ")).replace(ASYMP_UTF8
, ""));
752 // Coin Control: settings menu - coin control enabled/disabled by user
753 void SendCoinsDialog::coinControlFeatureChanged(bool checked
)
755 ui
->frameCoinControl
->setVisible(checked
);
757 if (!checked
&& model
) // coin control features disabled
758 CoinControlDialog::coinControl
->SetNull();
760 coinControlUpdateLabels();
763 // Coin Control: button inputs -> show actual coin control dialog
764 void SendCoinsDialog::coinControlButtonClicked()
766 CoinControlDialog
dlg(platformStyle
);
769 coinControlUpdateLabels();
772 // Coin Control: checkbox custom change address
773 void SendCoinsDialog::coinControlChangeChecked(int state
)
775 if (state
== Qt::Unchecked
)
777 CoinControlDialog::coinControl
->destChange
= CNoDestination();
778 ui
->labelCoinControlChangeLabel
->clear();
781 // use this to re-validate an already entered address
782 coinControlChangeEdited(ui
->lineEditCoinControlChange
->text());
784 ui
->lineEditCoinControlChange
->setEnabled((state
== Qt::Checked
));
787 // Coin Control: custom change address changed
788 void SendCoinsDialog::coinControlChangeEdited(const QString
& text
)
790 if (model
&& model
->getAddressTableModel())
792 // Default to no change address until verified
793 CoinControlDialog::coinControl
->destChange
= CNoDestination();
794 ui
->labelCoinControlChangeLabel
->setStyleSheet("QLabel{color:red;}");
796 const CTxDestination dest
= DecodeDestination(text
.toStdString());
798 if (text
.isEmpty()) // Nothing entered
800 ui
->labelCoinControlChangeLabel
->setText("");
802 else if (!IsValidDestination(dest
)) // Invalid address
804 ui
->labelCoinControlChangeLabel
->setText(tr("Warning: Invalid Bitcoin address"));
806 else // Valid address
808 if (!model
->IsSpendable(dest
)) {
809 ui
->labelCoinControlChangeLabel
->setText(tr("Warning: Unknown change address"));
811 // confirmation dialog
812 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?"),
813 QMessageBox::Yes
| QMessageBox::Cancel
, QMessageBox::Cancel
);
815 if(btnRetVal
== QMessageBox::Yes
)
816 CoinControlDialog::coinControl
->destChange
= dest
;
819 ui
->lineEditCoinControlChange
->setText("");
820 ui
->labelCoinControlChangeLabel
->setStyleSheet("QLabel{color:black;}");
821 ui
->labelCoinControlChangeLabel
->setText("");
824 else // Known change address
826 ui
->labelCoinControlChangeLabel
->setStyleSheet("QLabel{color:black;}");
829 QString associatedLabel
= model
->getAddressTableModel()->labelForAddress(text
);
830 if (!associatedLabel
.isEmpty())
831 ui
->labelCoinControlChangeLabel
->setText(associatedLabel
);
833 ui
->labelCoinControlChangeLabel
->setText(tr("(no label)"));
835 CoinControlDialog::coinControl
->destChange
= dest
;
841 // Coin Control: update labels
842 void SendCoinsDialog::coinControlUpdateLabels()
844 if (!model
|| !model
->getOptionsModel())
847 updateCoinControlState(*CoinControlDialog::coinControl
);
850 CoinControlDialog::payAmounts
.clear();
851 CoinControlDialog::fSubtractFeeFromAmount
= false;
853 for(int i
= 0; i
< ui
->entries
->count(); ++i
)
855 SendCoinsEntry
*entry
= qobject_cast
<SendCoinsEntry
*>(ui
->entries
->itemAt(i
)->widget());
856 if(entry
&& !entry
->isHidden())
858 SendCoinsRecipient rcp
= entry
->getValue();
859 CoinControlDialog::payAmounts
.append(rcp
.amount
);
860 if (rcp
.fSubtractFeeFromAmount
)
861 CoinControlDialog::fSubtractFeeFromAmount
= true;
865 if (CoinControlDialog::coinControl
->HasSelected())
867 // actual coin control calculation
868 CoinControlDialog::updateLabels(model
, this);
870 // show coin control stats
871 ui
->labelCoinControlAutomaticallySelected
->hide();
872 ui
->widgetCoinControl
->show();
876 // hide coin control stats
877 ui
->labelCoinControlAutomaticallySelected
->show();
878 ui
->widgetCoinControl
->hide();
879 ui
->labelCoinControlInsuffFunds
->hide();
883 SendConfirmationDialog::SendConfirmationDialog(const QString
&title
, const QString
&text
, int _secDelay
,
885 QMessageBox(QMessageBox::Question
, title
, text
, QMessageBox::Yes
| QMessageBox::Cancel
, parent
), secDelay(_secDelay
)
887 setDefaultButton(QMessageBox::Cancel
);
888 yesButton
= button(QMessageBox::Yes
);
890 connect(&countDownTimer
, SIGNAL(timeout()), this, SLOT(countDown()));
893 int SendConfirmationDialog::exec()
896 countDownTimer
.start(1000);
897 return QMessageBox::exec();
900 void SendConfirmationDialog::countDown()
907 countDownTimer
.stop();
911 void SendConfirmationDialog::updateYesButton()
915 yesButton
->setEnabled(false);
916 yesButton
->setText(tr("Yes") + " (" + QString::number(secDelay
) + ")");
920 yesButton
->setEnabled(true);
921 yesButton
->setText(tr("Yes"));