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(updateGlobalFeeVariables()));
179 connect(ui
->confTargetSelector
, SIGNAL(currentIndexChanged(int)), this, SLOT(coinControlUpdateLabels()));
180 connect(ui
->groupFee
, SIGNAL(buttonClicked(int)), this, SLOT(updateFeeSectionControls()));
181 connect(ui
->groupFee
, SIGNAL(buttonClicked(int)), this, SLOT(updateGlobalFeeVariables()));
182 connect(ui
->groupFee
, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels()));
183 connect(ui
->groupCustomFee
, SIGNAL(buttonClicked(int)), this, SLOT(updateGlobalFeeVariables()));
184 connect(ui
->groupCustomFee
, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels()));
185 connect(ui
->customFee
, SIGNAL(valueChanged()), this, SLOT(updateGlobalFeeVariables()));
186 connect(ui
->customFee
, SIGNAL(valueChanged()), this, SLOT(coinControlUpdateLabels()));
187 connect(ui
->checkBoxMinimumFee
, SIGNAL(stateChanged(int)), this, SLOT(setMinimumFee()));
188 connect(ui
->checkBoxMinimumFee
, SIGNAL(stateChanged(int)), this, SLOT(updateFeeSectionControls()));
189 connect(ui
->checkBoxMinimumFee
, SIGNAL(stateChanged(int)), this, SLOT(updateGlobalFeeVariables()));
190 connect(ui
->checkBoxMinimumFee
, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels()));
191 connect(ui
->optInRBF
, SIGNAL(stateChanged(int)), this, SLOT(updateSmartFeeLabel()));
192 connect(ui
->optInRBF
, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels()));
193 ui
->customFee
->setSingleStep(CWallet::GetRequiredFee(1000));
194 updateFeeSectionControls();
196 updateSmartFeeLabel();
197 updateGlobalFeeVariables();
199 // set default rbf checkbox state
200 ui
->optInRBF
->setCheckState(model
->getDefaultWalletRbf() ? Qt::Checked
: Qt::Unchecked
);
202 // set the smartfee-sliders default value (wallets default conf.target or last stored value)
204 if (settings
.value("nSmartFeeSliderPosition").toInt() != 0) {
205 // migrate nSmartFeeSliderPosition to nConfTarget
206 // nConfTarget is available since 0.15 (replaced nSmartFeeSliderPosition)
207 int nConfirmTarget
= 25 - settings
.value("nSmartFeeSliderPosition").toInt(); // 25 == old slider range
208 settings
.setValue("nConfTarget", nConfirmTarget
);
209 settings
.remove("nSmartFeeSliderPosition");
211 if (settings
.value("nConfTarget").toInt() == 0)
212 ui
->confTargetSelector
->setCurrentIndex(getIndexForConfTarget(model
->getDefaultConfirmTarget()));
214 ui
->confTargetSelector
->setCurrentIndex(getIndexForConfTarget(settings
.value("nConfTarget").toInt()));
218 SendCoinsDialog::~SendCoinsDialog()
221 settings
.setValue("fFeeSectionMinimized", fFeeMinimized
);
222 settings
.setValue("nFeeRadio", ui
->groupFee
->checkedId());
223 settings
.setValue("nCustomFeeRadio", ui
->groupCustomFee
->checkedId());
224 settings
.setValue("nConfTarget", getConfTargetForIndex(ui
->confTargetSelector
->currentIndex()));
225 settings
.setValue("nTransactionFee", (qint64
)ui
->customFee
->value());
226 settings
.setValue("fPayOnlyMinFee", ui
->checkBoxMinimumFee
->isChecked());
231 void SendCoinsDialog::on_sendButton_clicked()
233 if(!model
|| !model
->getOptionsModel())
236 QList
<SendCoinsRecipient
> recipients
;
239 for(int i
= 0; i
< ui
->entries
->count(); ++i
)
241 SendCoinsEntry
*entry
= qobject_cast
<SendCoinsEntry
*>(ui
->entries
->itemAt(i
)->widget());
244 if(entry
->validate())
246 recipients
.append(entry
->getValue());
255 if(!valid
|| recipients
.isEmpty())
260 fNewRecipientAllowed
= false;
261 WalletModel::UnlockContext
ctx(model
->requestUnlock());
264 // Unlock wallet was cancelled
265 fNewRecipientAllowed
= true;
269 // prepare transaction for getting txFee earlier
270 WalletModelTransaction
currentTransaction(recipients
);
271 WalletModel::SendCoinsReturn prepareStatus
;
273 // Always use a CCoinControl instance, use the CoinControlDialog instance if CoinControl has been enabled
275 if (model
->getOptionsModel()->getCoinControlFeatures())
276 ctrl
= *CoinControlDialog::coinControl
;
277 if (ui
->radioSmartFee
->isChecked())
278 ctrl
.nConfirmTarget
= getConfTargetForIndex(ui
->confTargetSelector
->currentIndex());
280 ctrl
.nConfirmTarget
= 0;
282 ctrl
.signalRbf
= ui
->optInRBF
->isChecked();
284 prepareStatus
= model
->prepareTransaction(currentTransaction
, &ctrl
);
286 // process prepareStatus and on error generate message shown to user
287 processSendCoinsReturn(prepareStatus
,
288 BitcoinUnits::formatWithUnit(model
->getOptionsModel()->getDisplayUnit(), currentTransaction
.getTransactionFee()));
290 if(prepareStatus
.status
!= WalletModel::OK
) {
291 fNewRecipientAllowed
= true;
295 CAmount txFee
= currentTransaction
.getTransactionFee();
297 // Format confirmation message
298 QStringList formatted
;
299 for (const SendCoinsRecipient
&rcp
: currentTransaction
.getRecipients())
301 // generate bold amount string
302 QString amount
= "<b>" + BitcoinUnits::formatHtmlWithUnit(model
->getOptionsModel()->getDisplayUnit(), rcp
.amount
);
303 amount
.append("</b>");
304 // generate monospace address string
305 QString address
= "<span style='font-family: monospace;'>" + rcp
.address
;
306 address
.append("</span>");
308 QString recipientElement
;
310 if (!rcp
.paymentRequest
.IsInitialized()) // normal payment
312 if(rcp
.label
.length() > 0) // label with address
314 recipientElement
= tr("%1 to %2").arg(amount
, GUIUtil::HtmlEscape(rcp
.label
));
315 recipientElement
.append(QString(" (%1)").arg(address
));
319 recipientElement
= tr("%1 to %2").arg(amount
, address
);
322 else if(!rcp
.authenticatedMerchant
.isEmpty()) // authenticated payment request
324 recipientElement
= tr("%1 to %2").arg(amount
, GUIUtil::HtmlEscape(rcp
.authenticatedMerchant
));
326 else // unauthenticated payment request
328 recipientElement
= tr("%1 to %2").arg(amount
, address
);
331 formatted
.append(recipientElement
);
334 QString questionString
= tr("Are you sure you want to send?");
335 questionString
.append("<br /><br />%1");
339 // append fee string if a fee is required
340 questionString
.append("<hr /><span style='color:#aa0000;'>");
341 questionString
.append(BitcoinUnits::formatHtmlWithUnit(model
->getOptionsModel()->getDisplayUnit(), txFee
));
342 questionString
.append("</span> ");
343 questionString
.append(tr("added as transaction fee"));
345 // append transaction size
346 questionString
.append(" (" + QString::number((double)currentTransaction
.getTransactionSize() / 1000) + " kB)");
349 // add total amount in all subdivision units
350 questionString
.append("<hr />");
351 CAmount totalAmount
= currentTransaction
.getTotalTransactionAmount() + txFee
;
352 QStringList alternativeUnits
;
353 for (BitcoinUnits::Unit u
: BitcoinUnits::availableUnits())
355 if(u
!= model
->getOptionsModel()->getDisplayUnit())
356 alternativeUnits
.append(BitcoinUnits::formatHtmlWithUnit(u
, totalAmount
));
358 questionString
.append(tr("Total Amount %1")
359 .arg(BitcoinUnits::formatHtmlWithUnit(model
->getOptionsModel()->getDisplayUnit(), totalAmount
)));
360 questionString
.append(QString("<span style='font-size:10pt;font-weight:normal;'><br />(=%2)</span>")
361 .arg(alternativeUnits
.join(" " + tr("or") + "<br />")));
363 if (ui
->optInRBF
->isChecked())
365 questionString
.append("<hr /><span>");
366 questionString
.append(tr("This transaction signals replaceability (optin-RBF)."));
367 questionString
.append("</span>");
370 SendConfirmationDialog
confirmationDialog(tr("Confirm send coins"),
371 questionString
.arg(formatted
.join("<br />")), SEND_CONFIRM_DELAY
, this);
372 confirmationDialog
.exec();
373 QMessageBox::StandardButton retval
= (QMessageBox::StandardButton
)confirmationDialog
.result();
375 if(retval
!= QMessageBox::Yes
)
377 fNewRecipientAllowed
= true;
381 // now send the prepared transaction
382 WalletModel::SendCoinsReturn sendStatus
= model
->sendCoins(currentTransaction
);
383 // process sendStatus and on error generate message shown to user
384 processSendCoinsReturn(sendStatus
);
386 if (sendStatus
.status
== WalletModel::OK
)
389 CoinControlDialog::coinControl
->UnSelectAll();
390 coinControlUpdateLabels();
392 fNewRecipientAllowed
= true;
395 void SendCoinsDialog::clear()
397 // Remove entries until only one left
398 while(ui
->entries
->count())
400 ui
->entries
->takeAt(0)->widget()->deleteLater();
404 updateTabsAndLabels();
407 void SendCoinsDialog::reject()
412 void SendCoinsDialog::accept()
417 SendCoinsEntry
*SendCoinsDialog::addEntry()
419 SendCoinsEntry
*entry
= new SendCoinsEntry(platformStyle
, this);
420 entry
->setModel(model
);
421 ui
->entries
->addWidget(entry
);
422 connect(entry
, SIGNAL(removeEntry(SendCoinsEntry
*)), this, SLOT(removeEntry(SendCoinsEntry
*)));
423 connect(entry
, SIGNAL(payAmountChanged()), this, SLOT(coinControlUpdateLabels()));
424 connect(entry
, SIGNAL(subtractFeeFromAmountChanged()), this, SLOT(coinControlUpdateLabels()));
426 // Focus the field, so that entry can start immediately
429 ui
->scrollAreaWidgetContents
->resize(ui
->scrollAreaWidgetContents
->sizeHint());
430 qApp
->processEvents();
431 QScrollBar
* bar
= ui
->scrollArea
->verticalScrollBar();
433 bar
->setSliderPosition(bar
->maximum());
435 updateTabsAndLabels();
439 void SendCoinsDialog::updateTabsAndLabels()
442 coinControlUpdateLabels();
445 void SendCoinsDialog::removeEntry(SendCoinsEntry
* entry
)
449 // If the last entry is about to be removed add an empty one
450 if (ui
->entries
->count() == 1)
453 entry
->deleteLater();
455 updateTabsAndLabels();
458 QWidget
*SendCoinsDialog::setupTabChain(QWidget
*prev
)
460 for(int i
= 0; i
< ui
->entries
->count(); ++i
)
462 SendCoinsEntry
*entry
= qobject_cast
<SendCoinsEntry
*>(ui
->entries
->itemAt(i
)->widget());
465 prev
= entry
->setupTabChain(prev
);
468 QWidget::setTabOrder(prev
, ui
->sendButton
);
469 QWidget::setTabOrder(ui
->sendButton
, ui
->clearButton
);
470 QWidget::setTabOrder(ui
->clearButton
, ui
->addButton
);
471 return ui
->addButton
;
474 void SendCoinsDialog::setAddress(const QString
&address
)
476 SendCoinsEntry
*entry
= 0;
477 // Replace the first entry if it is still unused
478 if(ui
->entries
->count() == 1)
480 SendCoinsEntry
*first
= qobject_cast
<SendCoinsEntry
*>(ui
->entries
->itemAt(0)->widget());
491 entry
->setAddress(address
);
494 void SendCoinsDialog::pasteEntry(const SendCoinsRecipient
&rv
)
496 if(!fNewRecipientAllowed
)
499 SendCoinsEntry
*entry
= 0;
500 // Replace the first entry if it is still unused
501 if(ui
->entries
->count() == 1)
503 SendCoinsEntry
*first
= qobject_cast
<SendCoinsEntry
*>(ui
->entries
->itemAt(0)->widget());
515 updateTabsAndLabels();
518 bool SendCoinsDialog::handlePaymentRequest(const SendCoinsRecipient
&rv
)
520 // Just paste the entry, all pre-checks
521 // are done in paymentserver.cpp.
526 void SendCoinsDialog::setBalance(const CAmount
& balance
, const CAmount
& unconfirmedBalance
, const CAmount
& immatureBalance
,
527 const CAmount
& watchBalance
, const CAmount
& watchUnconfirmedBalance
, const CAmount
& watchImmatureBalance
)
529 Q_UNUSED(unconfirmedBalance
);
530 Q_UNUSED(immatureBalance
);
531 Q_UNUSED(watchBalance
);
532 Q_UNUSED(watchUnconfirmedBalance
);
533 Q_UNUSED(watchImmatureBalance
);
535 if(model
&& model
->getOptionsModel())
537 ui
->labelBalance
->setText(BitcoinUnits::formatWithUnit(model
->getOptionsModel()->getDisplayUnit(), balance
));
541 void SendCoinsDialog::updateDisplayUnit()
543 setBalance(model
->getBalance(), 0, 0, 0, 0, 0);
544 ui
->customFee
->setDisplayUnit(model
->getOptionsModel()->getDisplayUnit());
546 updateSmartFeeLabel();
549 void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn
&sendCoinsReturn
, const QString
&msgArg
)
551 QPair
<QString
, CClientUIInterface::MessageBoxFlags
> msgParams
;
552 // Default to a warning message, override if error message is needed
553 msgParams
.second
= CClientUIInterface::MSG_WARNING
;
555 // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn.
556 // WalletModel::TransactionCommitFailed is used only in WalletModel::sendCoins()
557 // all others are used only in WalletModel::prepareTransaction()
558 switch(sendCoinsReturn
.status
)
560 case WalletModel::InvalidAddress
:
561 msgParams
.first
= tr("The recipient address is not valid. Please recheck.");
563 case WalletModel::InvalidAmount
:
564 msgParams
.first
= tr("The amount to pay must be larger than 0.");
566 case WalletModel::AmountExceedsBalance
:
567 msgParams
.first
= tr("The amount exceeds your balance.");
569 case WalletModel::AmountWithFeeExceedsBalance
:
570 msgParams
.first
= tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg
);
572 case WalletModel::DuplicateAddress
:
573 msgParams
.first
= tr("Duplicate address found: addresses should only be used once each.");
575 case WalletModel::TransactionCreationFailed
:
576 msgParams
.first
= tr("Transaction creation failed!");
577 msgParams
.second
= CClientUIInterface::MSG_ERROR
;
579 case WalletModel::TransactionCommitFailed
:
580 msgParams
.first
= tr("The transaction was rejected with the following reason: %1").arg(sendCoinsReturn
.reasonCommitFailed
);
581 msgParams
.second
= CClientUIInterface::MSG_ERROR
;
583 case WalletModel::AbsurdFee
:
584 msgParams
.first
= tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model
->getOptionsModel()->getDisplayUnit(), maxTxFee
));
586 case WalletModel::PaymentRequestExpired
:
587 msgParams
.first
= tr("Payment request expired.");
588 msgParams
.second
= CClientUIInterface::MSG_ERROR
;
590 // included to prevent a compiler warning.
591 case WalletModel::OK
:
596 Q_EMIT
message(tr("Send Coins"), msgParams
.first
, msgParams
.second
);
599 void SendCoinsDialog::minimizeFeeSection(bool fMinimize
)
601 ui
->labelFeeMinimized
->setVisible(fMinimize
);
602 ui
->buttonChooseFee
->setVisible(fMinimize
);
603 ui
->buttonMinimizeFee
->setVisible(!fMinimize
);
604 ui
->frameFeeSelection
->setVisible(!fMinimize
);
605 ui
->horizontalLayoutSmartFee
->setContentsMargins(0, (fMinimize
? 0 : 6), 0, 0);
606 fFeeMinimized
= fMinimize
;
609 void SendCoinsDialog::on_buttonChooseFee_clicked()
611 minimizeFeeSection(false);
614 void SendCoinsDialog::on_buttonMinimizeFee_clicked()
616 updateFeeMinimizedLabel();
617 minimizeFeeSection(true);
620 void SendCoinsDialog::setMinimumFee()
622 ui
->radioCustomPerKilobyte
->setChecked(true);
623 ui
->customFee
->setValue(CWallet::GetRequiredFee(1000));
626 void SendCoinsDialog::updateFeeSectionControls()
628 ui
->confTargetSelector
->setEnabled(ui
->radioSmartFee
->isChecked());
629 ui
->labelSmartFee
->setEnabled(ui
->radioSmartFee
->isChecked());
630 ui
->labelSmartFee2
->setEnabled(ui
->radioSmartFee
->isChecked());
631 ui
->labelSmartFee3
->setEnabled(ui
->radioSmartFee
->isChecked());
632 ui
->labelFeeEstimation
->setEnabled(ui
->radioSmartFee
->isChecked());
633 ui
->checkBoxMinimumFee
->setEnabled(ui
->radioCustomFee
->isChecked());
634 ui
->labelMinFeeWarning
->setEnabled(ui
->radioCustomFee
->isChecked());
635 ui
->radioCustomPerKilobyte
->setEnabled(ui
->radioCustomFee
->isChecked() && !ui
->checkBoxMinimumFee
->isChecked());
636 ui
->customFee
->setEnabled(ui
->radioCustomFee
->isChecked() && !ui
->checkBoxMinimumFee
->isChecked());
639 void SendCoinsDialog::updateGlobalFeeVariables()
641 if (ui
->radioSmartFee
->isChecked())
643 payTxFee
= CFeeRate(0);
647 payTxFee
= CFeeRate(ui
->customFee
->value());
651 void SendCoinsDialog::updateFeeMinimizedLabel()
653 if(!model
|| !model
->getOptionsModel())
656 if (ui
->radioSmartFee
->isChecked())
657 ui
->labelFeeMinimized
->setText(ui
->labelSmartFee
->text());
659 ui
->labelFeeMinimized
->setText(BitcoinUnits::formatWithUnit(model
->getOptionsModel()->getDisplayUnit(), ui
->customFee
->value()) +
660 ((ui
->radioCustomPerKilobyte
->isChecked()) ? "/kB" : ""));
664 void SendCoinsDialog::updateMinFeeLabel()
666 if (model
&& model
->getOptionsModel())
667 ui
->checkBoxMinimumFee
->setText(tr("Pay only the required fee of %1").arg(
668 BitcoinUnits::formatWithUnit(model
->getOptionsModel()->getDisplayUnit(), CWallet::GetRequiredFee(1000)) + "/kB")
672 void SendCoinsDialog::updateSmartFeeLabel()
674 if(!model
|| !model
->getOptionsModel())
677 int nBlocksToConfirm
= getConfTargetForIndex(ui
->confTargetSelector
->currentIndex());
678 FeeCalculation feeCalc
;
679 bool conservative_estimate
= CalculateEstimateType(FeeEstimateMode::UNSET
, ui
->optInRBF
->isChecked());
680 CFeeRate feeRate
= ::feeEstimator
.estimateSmartFee(nBlocksToConfirm
, &feeCalc
, ::mempool
, conservative_estimate
);
681 if (feeRate
<= CFeeRate(0)) // not enough data => minfee
683 ui
->labelSmartFee
->setText(BitcoinUnits::formatWithUnit(model
->getOptionsModel()->getDisplayUnit(),
684 std::max(CWallet::fallbackFee
.GetFeePerK(), CWallet::GetRequiredFee(1000))) + "/kB");
685 ui
->labelSmartFee2
->show(); // (Smart fee not initialized yet. This usually takes a few blocks...)
686 ui
->labelFeeEstimation
->setText("");
687 ui
->fallbackFeeWarningLabel
->setVisible(true);
688 int lightness
= ui
->fallbackFeeWarningLabel
->palette().color(QPalette::WindowText
).lightness();
689 QColor
warning_colour(255 - (lightness
/ 5), 176 - (lightness
/ 3), 48 - (lightness
/ 14));
690 ui
->fallbackFeeWarningLabel
->setStyleSheet("QLabel { color: " + warning_colour
.name() + "; }");
691 ui
->fallbackFeeWarningLabel
->setIndent(QFontMetrics(ui
->fallbackFeeWarningLabel
->font()).width("x"));
695 ui
->labelSmartFee
->setText(BitcoinUnits::formatWithUnit(model
->getOptionsModel()->getDisplayUnit(),
696 std::max(feeRate
.GetFeePerK(), CWallet::GetRequiredFee(1000))) + "/kB");
697 ui
->labelSmartFee2
->hide();
698 ui
->labelFeeEstimation
->setText(tr("Estimated to begin confirmation within %n block(s).", "", feeCalc
.returnedTarget
));
699 ui
->fallbackFeeWarningLabel
->setVisible(false);
702 updateFeeMinimizedLabel();
705 // Coin Control: copy label "Quantity" to clipboard
706 void SendCoinsDialog::coinControlClipboardQuantity()
708 GUIUtil::setClipboard(ui
->labelCoinControlQuantity
->text());
711 // Coin Control: copy label "Amount" to clipboard
712 void SendCoinsDialog::coinControlClipboardAmount()
714 GUIUtil::setClipboard(ui
->labelCoinControlAmount
->text().left(ui
->labelCoinControlAmount
->text().indexOf(" ")));
717 // Coin Control: copy label "Fee" to clipboard
718 void SendCoinsDialog::coinControlClipboardFee()
720 GUIUtil::setClipboard(ui
->labelCoinControlFee
->text().left(ui
->labelCoinControlFee
->text().indexOf(" ")).replace(ASYMP_UTF8
, ""));
723 // Coin Control: copy label "After fee" to clipboard
724 void SendCoinsDialog::coinControlClipboardAfterFee()
726 GUIUtil::setClipboard(ui
->labelCoinControlAfterFee
->text().left(ui
->labelCoinControlAfterFee
->text().indexOf(" ")).replace(ASYMP_UTF8
, ""));
729 // Coin Control: copy label "Bytes" to clipboard
730 void SendCoinsDialog::coinControlClipboardBytes()
732 GUIUtil::setClipboard(ui
->labelCoinControlBytes
->text().replace(ASYMP_UTF8
, ""));
735 // Coin Control: copy label "Dust" to clipboard
736 void SendCoinsDialog::coinControlClipboardLowOutput()
738 GUIUtil::setClipboard(ui
->labelCoinControlLowOutput
->text());
741 // Coin Control: copy label "Change" to clipboard
742 void SendCoinsDialog::coinControlClipboardChange()
744 GUIUtil::setClipboard(ui
->labelCoinControlChange
->text().left(ui
->labelCoinControlChange
->text().indexOf(" ")).replace(ASYMP_UTF8
, ""));
747 // Coin Control: settings menu - coin control enabled/disabled by user
748 void SendCoinsDialog::coinControlFeatureChanged(bool checked
)
750 ui
->frameCoinControl
->setVisible(checked
);
752 if (!checked
&& model
) // coin control features disabled
753 CoinControlDialog::coinControl
->SetNull();
755 // make sure we set back the confirmation target
756 updateGlobalFeeVariables();
757 coinControlUpdateLabels();
760 // Coin Control: button inputs -> show actual coin control dialog
761 void SendCoinsDialog::coinControlButtonClicked()
763 CoinControlDialog
dlg(platformStyle
);
766 coinControlUpdateLabels();
769 // Coin Control: checkbox custom change address
770 void SendCoinsDialog::coinControlChangeChecked(int state
)
772 if (state
== Qt::Unchecked
)
774 CoinControlDialog::coinControl
->destChange
= CNoDestination();
775 ui
->labelCoinControlChangeLabel
->clear();
778 // use this to re-validate an already entered address
779 coinControlChangeEdited(ui
->lineEditCoinControlChange
->text());
781 ui
->lineEditCoinControlChange
->setEnabled((state
== Qt::Checked
));
784 // Coin Control: custom change address changed
785 void SendCoinsDialog::coinControlChangeEdited(const QString
& text
)
787 if (model
&& model
->getAddressTableModel())
789 // Default to no change address until verified
790 CoinControlDialog::coinControl
->destChange
= CNoDestination();
791 ui
->labelCoinControlChangeLabel
->setStyleSheet("QLabel{color:red;}");
793 CBitcoinAddress addr
= CBitcoinAddress(text
.toStdString());
795 if (text
.isEmpty()) // Nothing entered
797 ui
->labelCoinControlChangeLabel
->setText("");
799 else if (!addr
.IsValid()) // Invalid address
801 ui
->labelCoinControlChangeLabel
->setText(tr("Warning: Invalid Bitcoin address"));
803 else // Valid address
806 addr
.GetKeyID(keyid
);
807 if (!model
->havePrivKey(keyid
)) // Unknown change address
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
= addr
.Get();
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
= addr
.Get();
841 // Coin Control: update labels
842 void SendCoinsDialog::coinControlUpdateLabels()
844 if (!model
|| !model
->getOptionsModel())
848 CoinControlDialog::payAmounts
.clear();
849 CoinControlDialog::fSubtractFeeFromAmount
= false;
850 if (ui
->radioSmartFee
->isChecked()) {
851 CoinControlDialog::coinControl
->nConfirmTarget
= getConfTargetForIndex(ui
->confTargetSelector
->currentIndex());
853 CoinControlDialog::coinControl
->nConfirmTarget
= model
->getDefaultConfirmTarget();
855 CoinControlDialog::coinControl
->signalRbf
= ui
->optInRBF
->isChecked();
857 for(int i
= 0; i
< ui
->entries
->count(); ++i
)
859 SendCoinsEntry
*entry
= qobject_cast
<SendCoinsEntry
*>(ui
->entries
->itemAt(i
)->widget());
860 if(entry
&& !entry
->isHidden())
862 SendCoinsRecipient rcp
= entry
->getValue();
863 CoinControlDialog::payAmounts
.append(rcp
.amount
);
864 if (rcp
.fSubtractFeeFromAmount
)
865 CoinControlDialog::fSubtractFeeFromAmount
= true;
869 if (CoinControlDialog::coinControl
->HasSelected())
871 // actual coin control calculation
872 CoinControlDialog::updateLabels(model
, this);
874 // show coin control stats
875 ui
->labelCoinControlAutomaticallySelected
->hide();
876 ui
->widgetCoinControl
->show();
880 // hide coin control stats
881 ui
->labelCoinControlAutomaticallySelected
->show();
882 ui
->widgetCoinControl
->hide();
883 ui
->labelCoinControlInsuffFunds
->hide();
887 SendConfirmationDialog::SendConfirmationDialog(const QString
&title
, const QString
&text
, int _secDelay
,
889 QMessageBox(QMessageBox::Question
, title
, text
, QMessageBox::Yes
| QMessageBox::Cancel
, parent
), secDelay(_secDelay
)
891 setDefaultButton(QMessageBox::Cancel
);
892 yesButton
= button(QMessageBox::Yes
);
894 connect(&countDownTimer
, SIGNAL(timeout()), this, SLOT(countDown()));
897 int SendConfirmationDialog::exec()
900 countDownTimer
.start(1000);
901 return QMessageBox::exec();
904 void SendConfirmationDialog::countDown()
911 countDownTimer
.stop();
915 void SendConfirmationDialog::updateYesButton()
919 yesButton
->setEnabled(false);
920 yesButton
->setText(tr("Yes") + " (" + QString::number(secDelay
) + ")");
924 yesButton
->setEnabled(true);
925 yesButton
->setText(tr("Yes"));