1 // Copyright (c) 2011-2015 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.
4
5 #include "sendcoinsdialog.h"
6 #include "ui_sendcoinsdialog.h"
7
8 #include "addresstablemodel.h"
9 #include "bitcoinunits.h"
10 #include "clientmodel.h"
11 #include "coincontroldialog.h"
12 #include "guiutil.h"
13 #include "optionsmodel.h"
14 #include "platformstyle.h"
15 #include "sendcoinsentry.h"
16 #include "walletmodel.h"
17
18 #include "base58.h"
19 #include "coincontrol.h"
20 #include "main.h" // mempool and minRelayTxFee
21 #include "ui_interface.h"
22 #include "txmempool.h"
23 #include "wallet/wallet.h"
24
25 #include <QMessageBox>
26 #include <QScrollBar>
27 #include <QSettings>
28 #include <QTextDocument>
29 #include <QTimer>
30
31 #define SEND_CONFIRM_DELAY 3
32
SendCoinsDialog(const PlatformStyle * platformStyle,QWidget * parent)33 SendCoinsDialog::SendCoinsDialog(const PlatformStyle *platformStyle, QWidget *parent) :
34 QDialog(parent),
35 ui(new Ui::SendCoinsDialog),
36 clientModel(0),
37 model(0),
38 fNewRecipientAllowed(true),
39 fFeeMinimized(true),
40 platformStyle(platformStyle)
41 {
42 ui->setupUi(this);
43
44 if (!platformStyle->getImagesOnButtons()) {
45 ui->addButton->setIcon(QIcon());
46 ui->clearButton->setIcon(QIcon());
47 ui->sendButton->setIcon(QIcon());
48 } else {
49 ui->addButton->setIcon(platformStyle->SingleColorIcon(":/icons/add"));
50 ui->clearButton->setIcon(platformStyle->SingleColorIcon(":/icons/remove"));
51 ui->sendButton->setIcon(platformStyle->SingleColorIcon(":/icons/send"));
52 }
53
54 GUIUtil::setupAddressWidget(ui->lineEditCoinControlChange, this);
55
56 addEntry();
57
58 connect(ui->addButton, SIGNAL(clicked()), this, SLOT(addEntry()));
59 connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear()));
60
61 // Coin Control
62 connect(ui->pushButtonCoinControl, SIGNAL(clicked()), this, SLOT(coinControlButtonClicked()));
63 connect(ui->checkBoxCoinControlChange, SIGNAL(stateChanged(int)), this, SLOT(coinControlChangeChecked(int)));
64 connect(ui->lineEditCoinControlChange, SIGNAL(textEdited(const QString &)), this, SLOT(coinControlChangeEdited(const QString &)));
65
66 // Coin Control: clipboard actions
67 QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this);
68 QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this);
69 QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this);
70 QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this);
71 QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this);
72 QAction *clipboardPriorityAction = new QAction(tr("Copy priority"), this);
73 QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this);
74 QAction *clipboardChangeAction = new QAction(tr("Copy change"), this);
75 connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardQuantity()));
76 connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAmount()));
77 connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardFee()));
78 connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAfterFee()));
79 connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardBytes()));
80 connect(clipboardPriorityAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardPriority()));
81 connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardLowOutput()));
82 connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardChange()));
83 ui->labelCoinControlQuantity->addAction(clipboardQuantityAction);
84 ui->labelCoinControlAmount->addAction(clipboardAmountAction);
85 ui->labelCoinControlFee->addAction(clipboardFeeAction);
86 ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction);
87 ui->labelCoinControlBytes->addAction(clipboardBytesAction);
88 ui->labelCoinControlPriority->addAction(clipboardPriorityAction);
89 ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction);
90 ui->labelCoinControlChange->addAction(clipboardChangeAction);
91
92 // init transaction fee section
93 QSettings settings;
94 if (!settings.contains("fFeeSectionMinimized"))
95 settings.setValue("fFeeSectionMinimized", true);
96 if (!settings.contains("nFeeRadio") && settings.contains("nTransactionFee") && settings.value("nTransactionFee").toLongLong() > 0) // compatibility
97 settings.setValue("nFeeRadio", 1); // custom
98 if (!settings.contains("nFeeRadio"))
99 settings.setValue("nFeeRadio", 0); // recommended
100 if (!settings.contains("nCustomFeeRadio") && settings.contains("nTransactionFee") && settings.value("nTransactionFee").toLongLong() > 0) // compatibility
101 settings.setValue("nCustomFeeRadio", 1); // total at least
102 if (!settings.contains("nCustomFeeRadio"))
103 settings.setValue("nCustomFeeRadio", 0); // per kilobyte
104 if (!settings.contains("nSmartFeeSliderPosition"))
105 settings.setValue("nSmartFeeSliderPosition", 0);
106 if (!settings.contains("nTransactionFee"))
107 settings.setValue("nTransactionFee", (qint64)DEFAULT_TRANSACTION_FEE);
108 if (!settings.contains("fPayOnlyMinFee"))
109 settings.setValue("fPayOnlyMinFee", false);
110 ui->groupFee->setId(ui->radioSmartFee, 0);
111 ui->groupFee->setId(ui->radioCustomFee, 1);
112 ui->groupFee->button((int)std::max(0, std::min(1, settings.value("nFeeRadio").toInt())))->setChecked(true);
113 ui->groupCustomFee->setId(ui->radioCustomPerKilobyte, 0);
114 ui->groupCustomFee->setId(ui->radioCustomAtLeast, 1);
115 ui->groupCustomFee->button((int)std::max(0, std::min(1, settings.value("nCustomFeeRadio").toInt())))->setChecked(true);
116 ui->sliderSmartFee->setValue(settings.value("nSmartFeeSliderPosition").toInt());
117 ui->customFee->setValue(settings.value("nTransactionFee").toLongLong());
118 ui->checkBoxMinimumFee->setChecked(settings.value("fPayOnlyMinFee").toBool());
119 minimizeFeeSection(settings.value("fFeeSectionMinimized").toBool());
120 }
121
setClientModel(ClientModel * clientModel)122 void SendCoinsDialog::setClientModel(ClientModel *clientModel)
123 {
124 this->clientModel = clientModel;
125
126 if (clientModel) {
127 connect(clientModel, SIGNAL(numBlocksChanged(int,QDateTime,double,bool)), this, SLOT(updateSmartFeeLabel()));
128 }
129 }
130
setModel(WalletModel * model)131 void SendCoinsDialog::setModel(WalletModel *model)
132 {
133 this->model = model;
134
135 if(model && model->getOptionsModel())
136 {
137 for(int i = 0; i < ui->entries->count(); ++i)
138 {
139 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
140 if(entry)
141 {
142 entry->setModel(model);
143 }
144 }
145
146 setBalance(model->getBalance(), model->getUnconfirmedBalance(), model->getImmatureBalance(),
147 model->getWatchBalance(), model->getWatchUnconfirmedBalance(), model->getWatchImmatureBalance());
148 connect(model, SIGNAL(balanceChanged(CAmount,CAmount,CAmount,CAmount,CAmount,CAmount)), this, SLOT(setBalance(CAmount,CAmount,CAmount,CAmount,CAmount,CAmount)));
149 connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
150 updateDisplayUnit();
151
152 // Coin Control
153 connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(coinControlUpdateLabels()));
154 connect(model->getOptionsModel(), SIGNAL(coinControlFeaturesChanged(bool)), this, SLOT(coinControlFeatureChanged(bool)));
155 ui->frameCoinControl->setVisible(model->getOptionsModel()->getCoinControlFeatures());
156 coinControlUpdateLabels();
157
158 // fee section
159 connect(ui->sliderSmartFee, SIGNAL(valueChanged(int)), this, SLOT(updateSmartFeeLabel()));
160 connect(ui->sliderSmartFee, SIGNAL(valueChanged(int)), this, SLOT(updateGlobalFeeVariables()));
161 connect(ui->sliderSmartFee, SIGNAL(valueChanged(int)), this, SLOT(coinControlUpdateLabels()));
162 connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(updateFeeSectionControls()));
163 connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(updateGlobalFeeVariables()));
164 connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels()));
165 connect(ui->groupCustomFee, SIGNAL(buttonClicked(int)), this, SLOT(updateGlobalFeeVariables()));
166 connect(ui->groupCustomFee, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels()));
167 connect(ui->customFee, SIGNAL(valueChanged()), this, SLOT(updateGlobalFeeVariables()));
168 connect(ui->customFee, SIGNAL(valueChanged()), this, SLOT(coinControlUpdateLabels()));
169 connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(setMinimumFee()));
170 connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateFeeSectionControls()));
171 connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateGlobalFeeVariables()));
172 connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels()));
173 ui->customFee->setSingleStep(CWallet::GetRequiredFee(1000));
174 updateFeeSectionControls();
175 updateMinFeeLabel();
176 updateSmartFeeLabel();
177 updateGlobalFeeVariables();
178 }
179 }
180
~SendCoinsDialog()181 SendCoinsDialog::~SendCoinsDialog()
182 {
183 QSettings settings;
184 settings.setValue("fFeeSectionMinimized", fFeeMinimized);
185 settings.setValue("nFeeRadio", ui->groupFee->checkedId());
186 settings.setValue("nCustomFeeRadio", ui->groupCustomFee->checkedId());
187 settings.setValue("nSmartFeeSliderPosition", ui->sliderSmartFee->value());
188 settings.setValue("nTransactionFee", (qint64)ui->customFee->value());
189 settings.setValue("fPayOnlyMinFee", ui->checkBoxMinimumFee->isChecked());
190
191 delete ui;
192 }
193
on_sendButton_clicked()194 void SendCoinsDialog::on_sendButton_clicked()
195 {
196 if(!model || !model->getOptionsModel())
197 return;
198
199 QList<SendCoinsRecipient> recipients;
200 bool valid = true;
201
202 for(int i = 0; i < ui->entries->count(); ++i)
203 {
204 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
205 if(entry)
206 {
207 if(entry->validate())
208 {
209 recipients.append(entry->getValue());
210 }
211 else
212 {
213 valid = false;
214 }
215 }
216 }
217
218 if(!valid || recipients.isEmpty())
219 {
220 return;
221 }
222
223 fNewRecipientAllowed = false;
224 WalletModel::UnlockContext ctx(model->requestUnlock());
225 if(!ctx.isValid())
226 {
227 // Unlock wallet was cancelled
228 fNewRecipientAllowed = true;
229 return;
230 }
231
232 // prepare transaction for getting txFee earlier
233 WalletModelTransaction currentTransaction(recipients);
234 WalletModel::SendCoinsReturn prepareStatus;
235 if (model->getOptionsModel()->getCoinControlFeatures()) // coin control enabled
236 prepareStatus = model->prepareTransaction(currentTransaction, CoinControlDialog::coinControl);
237 else
238 prepareStatus = model->prepareTransaction(currentTransaction);
239
240 // process prepareStatus and on error generate message shown to user
241 processSendCoinsReturn(prepareStatus,
242 BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTransactionFee()));
243
244 if(prepareStatus.status != WalletModel::OK) {
245 fNewRecipientAllowed = true;
246 return;
247 }
248
249 CAmount txFee = currentTransaction.getTransactionFee();
250
251 // Format confirmation message
252 QStringList formatted;
253 Q_FOREACH(const SendCoinsRecipient &rcp, currentTransaction.getRecipients())
254 {
255 // generate bold amount string
256 QString amount = "<b>" + BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
257 amount.append("</b>");
258 // generate monospace address string
259 QString address = "<span style='font-family: monospace;'>" + rcp.address;
260 address.append("</span>");
261
262 QString recipientElement;
263
264 if (!rcp.paymentRequest.IsInitialized()) // normal payment
265 {
266 if(rcp.label.length() > 0) // label with address
267 {
268 recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.label));
269 recipientElement.append(QString(" (%1)").arg(address));
270 }
271 else // just address
272 {
273 recipientElement = tr("%1 to %2").arg(amount, address);
274 }
275 }
276 else if(!rcp.authenticatedMerchant.isEmpty()) // authenticated payment request
277 {
278 recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.authenticatedMerchant));
279 }
280 else // unauthenticated payment request
281 {
282 recipientElement = tr("%1 to %2").arg(amount, address);
283 }
284
285 formatted.append(recipientElement);
286 }
287
288 QString questionString = tr("Are you sure you want to send?");
289 questionString.append("<br /><br />%1");
290
291 if(txFee > 0)
292 {
293 // append fee string if a fee is required
294 questionString.append("<hr /><span style='color:#aa0000;'>");
295 questionString.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee));
296 questionString.append("</span> ");
297 questionString.append(tr("added as transaction fee"));
298
299 // append transaction size
300 questionString.append(" (" + QString::number((double)currentTransaction.getTransactionSize() / 1000) + " kB)");
301 }
302
303 // add total amount in all subdivision units
304 questionString.append("<hr />");
305 CAmount totalAmount = currentTransaction.getTotalTransactionAmount() + txFee;
306 QStringList alternativeUnits;
307 Q_FOREACH(BitcoinUnits::Unit u, BitcoinUnits::availableUnits())
308 {
309 if(u != model->getOptionsModel()->getDisplayUnit())
310 alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
311 }
312 questionString.append(tr("Total Amount %1")
313 .arg(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), totalAmount)));
314 questionString.append(QString("<span style='font-size:10pt;font-weight:normal;'><br />(=%2)</span>")
315 .arg(alternativeUnits.join(" " + tr("or") + "<br />")));
316
317 SendConfirmationDialog confirmationDialog(tr("Confirm send coins"),
318 questionString.arg(formatted.join("<br />")), SEND_CONFIRM_DELAY, this);
319 confirmationDialog.exec();
320 QMessageBox::StandardButton retval = (QMessageBox::StandardButton)confirmationDialog.result();
321
322 if(retval != QMessageBox::Yes)
323 {
324 fNewRecipientAllowed = true;
325 return;
326 }
327
328 // now send the prepared transaction
329 WalletModel::SendCoinsReturn sendStatus = model->sendCoins(currentTransaction);
330 // process sendStatus and on error generate message shown to user
331 processSendCoinsReturn(sendStatus);
332
333 if (sendStatus.status == WalletModel::OK)
334 {
335 accept();
336 CoinControlDialog::coinControl->UnSelectAll();
337 coinControlUpdateLabels();
338 }
339 fNewRecipientAllowed = true;
340 }
341
clear()342 void SendCoinsDialog::clear()
343 {
344 // Remove entries until only one left
345 while(ui->entries->count())
346 {
347 ui->entries->takeAt(0)->widget()->deleteLater();
348 }
349 addEntry();
350
351 updateTabsAndLabels();
352 }
353
reject()354 void SendCoinsDialog::reject()
355 {
356 clear();
357 }
358
accept()359 void SendCoinsDialog::accept()
360 {
361 clear();
362 }
363
addEntry()364 SendCoinsEntry *SendCoinsDialog::addEntry()
365 {
366 SendCoinsEntry *entry = new SendCoinsEntry(platformStyle, this);
367 entry->setModel(model);
368 ui->entries->addWidget(entry);
369 connect(entry, SIGNAL(removeEntry(SendCoinsEntry*)), this, SLOT(removeEntry(SendCoinsEntry*)));
370 connect(entry, SIGNAL(payAmountChanged()), this, SLOT(coinControlUpdateLabels()));
371 connect(entry, SIGNAL(subtractFeeFromAmountChanged()), this, SLOT(coinControlUpdateLabels()));
372
373 // Focus the field, so that entry can start immediately
374 entry->clear();
375 entry->setFocus();
376 ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
377 qApp->processEvents();
378 QScrollBar* bar = ui->scrollArea->verticalScrollBar();
379 if(bar)
380 bar->setSliderPosition(bar->maximum());
381
382 updateTabsAndLabels();
383 return entry;
384 }
385
updateTabsAndLabels()386 void SendCoinsDialog::updateTabsAndLabels()
387 {
388 setupTabChain(0);
389 coinControlUpdateLabels();
390 }
391
removeEntry(SendCoinsEntry * entry)392 void SendCoinsDialog::removeEntry(SendCoinsEntry* entry)
393 {
394 entry->hide();
395
396 // If the last entry is about to be removed add an empty one
397 if (ui->entries->count() == 1)
398 addEntry();
399
400 entry->deleteLater();
401
402 updateTabsAndLabels();
403 }
404
setupTabChain(QWidget * prev)405 QWidget *SendCoinsDialog::setupTabChain(QWidget *prev)
406 {
407 for(int i = 0; i < ui->entries->count(); ++i)
408 {
409 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
410 if(entry)
411 {
412 prev = entry->setupTabChain(prev);
413 }
414 }
415 QWidget::setTabOrder(prev, ui->sendButton);
416 QWidget::setTabOrder(ui->sendButton, ui->clearButton);
417 QWidget::setTabOrder(ui->clearButton, ui->addButton);
418 return ui->addButton;
419 }
420
setAddress(const QString & address)421 void SendCoinsDialog::setAddress(const QString &address)
422 {
423 SendCoinsEntry *entry = 0;
424 // Replace the first entry if it is still unused
425 if(ui->entries->count() == 1)
426 {
427 SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
428 if(first->isClear())
429 {
430 entry = first;
431 }
432 }
433 if(!entry)
434 {
435 entry = addEntry();
436 }
437
438 entry->setAddress(address);
439 }
440
pasteEntry(const SendCoinsRecipient & rv)441 void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv)
442 {
443 if(!fNewRecipientAllowed)
444 return;
445
446 SendCoinsEntry *entry = 0;
447 // Replace the first entry if it is still unused
448 if(ui->entries->count() == 1)
449 {
450 SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
451 if(first->isClear())
452 {
453 entry = first;
454 }
455 }
456 if(!entry)
457 {
458 entry = addEntry();
459 }
460
461 entry->setValue(rv);
462 updateTabsAndLabels();
463 }
464
handlePaymentRequest(const SendCoinsRecipient & rv)465 bool SendCoinsDialog::handlePaymentRequest(const SendCoinsRecipient &rv)
466 {
467 // Just paste the entry, all pre-checks
468 // are done in paymentserver.cpp.
469 pasteEntry(rv);
470 return true;
471 }
472
setBalance(const CAmount & balance,const CAmount & unconfirmedBalance,const CAmount & immatureBalance,const CAmount & watchBalance,const CAmount & watchUnconfirmedBalance,const CAmount & watchImmatureBalance)473 void SendCoinsDialog::setBalance(const CAmount& balance, const CAmount& unconfirmedBalance, const CAmount& immatureBalance,
474 const CAmount& watchBalance, const CAmount& watchUnconfirmedBalance, const CAmount& watchImmatureBalance)
475 {
476 Q_UNUSED(unconfirmedBalance);
477 Q_UNUSED(immatureBalance);
478 Q_UNUSED(watchBalance);
479 Q_UNUSED(watchUnconfirmedBalance);
480 Q_UNUSED(watchImmatureBalance);
481
482 if(model && model->getOptionsModel())
483 {
484 ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), balance));
485 }
486 }
487
updateDisplayUnit()488 void SendCoinsDialog::updateDisplayUnit()
489 {
490 setBalance(model->getBalance(), 0, 0, 0, 0, 0);
491 ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
492 updateMinFeeLabel();
493 updateSmartFeeLabel();
494 }
495
processSendCoinsReturn(const WalletModel::SendCoinsReturn & sendCoinsReturn,const QString & msgArg)496 void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg)
497 {
498 QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
499 // Default to a warning message, override if error message is needed
500 msgParams.second = CClientUIInterface::MSG_WARNING;
501
502 // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn.
503 // WalletModel::TransactionCommitFailed is used only in WalletModel::sendCoins()
504 // all others are used only in WalletModel::prepareTransaction()
505 switch(sendCoinsReturn.status)
506 {
507 case WalletModel::InvalidAddress:
508 msgParams.first = tr("The recipient address is not valid. Please recheck.");
509 break;
510 case WalletModel::InvalidAmount:
511 msgParams.first = tr("The amount to pay must be larger than 0.");
512 break;
513 case WalletModel::AmountExceedsBalance:
514 msgParams.first = tr("The amount exceeds your balance.");
515 break;
516 case WalletModel::AmountWithFeeExceedsBalance:
517 msgParams.first = tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg);
518 break;
519 case WalletModel::DuplicateAddress:
520 msgParams.first = tr("Duplicate address found: addresses should only be used once each.");
521 break;
522 case WalletModel::TransactionCreationFailed:
523 msgParams.first = tr("Transaction creation failed!");
524 msgParams.second = CClientUIInterface::MSG_ERROR;
525 break;
526 case WalletModel::TransactionCommitFailed:
527 msgParams.first = tr("The transaction was rejected! This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here.");
528 msgParams.second = CClientUIInterface::MSG_ERROR;
529 break;
530 case WalletModel::AbsurdFee:
531 msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), maxTxFee));
532 break;
533 case WalletModel::PaymentRequestExpired:
534 msgParams.first = tr("Payment request expired.");
535 msgParams.second = CClientUIInterface::MSG_ERROR;
536 break;
537 // included to prevent a compiler warning.
538 case WalletModel::OK:
539 default:
540 return;
541 }
542
543 Q_EMIT message(tr("Send Coins"), msgParams.first, msgParams.second);
544 }
545
minimizeFeeSection(bool fMinimize)546 void SendCoinsDialog::minimizeFeeSection(bool fMinimize)
547 {
548 ui->labelFeeMinimized->setVisible(fMinimize);
549 ui->buttonChooseFee ->setVisible(fMinimize);
550 ui->buttonMinimizeFee->setVisible(!fMinimize);
551 ui->frameFeeSelection->setVisible(!fMinimize);
552 ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0);
553 fFeeMinimized = fMinimize;
554 }
555
on_buttonChooseFee_clicked()556 void SendCoinsDialog::on_buttonChooseFee_clicked()
557 {
558 minimizeFeeSection(false);
559 }
560
on_buttonMinimizeFee_clicked()561 void SendCoinsDialog::on_buttonMinimizeFee_clicked()
562 {
563 updateFeeMinimizedLabel();
564 minimizeFeeSection(true);
565 }
566
setMinimumFee()567 void SendCoinsDialog::setMinimumFee()
568 {
569 ui->radioCustomPerKilobyte->setChecked(true);
570 ui->customFee->setValue(CWallet::GetRequiredFee(1000));
571 }
572
updateFeeSectionControls()573 void SendCoinsDialog::updateFeeSectionControls()
574 {
575 ui->sliderSmartFee ->setEnabled(ui->radioSmartFee->isChecked());
576 ui->labelSmartFee ->setEnabled(ui->radioSmartFee->isChecked());
577 ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked());
578 ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked());
579 ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked());
580 ui->labelSmartFeeNormal ->setEnabled(ui->radioSmartFee->isChecked());
581 ui->labelSmartFeeFast ->setEnabled(ui->radioSmartFee->isChecked());
582 ui->checkBoxMinimumFee ->setEnabled(ui->radioCustomFee->isChecked());
583 ui->labelMinFeeWarning ->setEnabled(ui->radioCustomFee->isChecked());
584 ui->radioCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked());
585 ui->radioCustomAtLeast ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked() && CoinControlDialog::coinControl->HasSelected());
586 ui->customFee ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked());
587 }
588
updateGlobalFeeVariables()589 void SendCoinsDialog::updateGlobalFeeVariables()
590 {
591 if (ui->radioSmartFee->isChecked())
592 {
593 nTxConfirmTarget = defaultConfirmTarget - ui->sliderSmartFee->value();
594 payTxFee = CFeeRate(0);
595
596 // set nMinimumTotalFee to 0 to not accidentally pay a custom fee
597 CoinControlDialog::coinControl->nMinimumTotalFee = 0;
598 }
599 else
600 {
601 nTxConfirmTarget = defaultConfirmTarget;
602 payTxFee = CFeeRate(ui->customFee->value());
603
604 // if user has selected to set a minimum absolute fee, pass the value to coincontrol
605 // set nMinimumTotalFee to 0 in case of user has selected that the fee is per KB
606 CoinControlDialog::coinControl->nMinimumTotalFee = ui->radioCustomAtLeast->isChecked() ? ui->customFee->value() : 0;
607 }
608 }
609
updateFeeMinimizedLabel()610 void SendCoinsDialog::updateFeeMinimizedLabel()
611 {
612 if(!model || !model->getOptionsModel())
613 return;
614
615 if (ui->radioSmartFee->isChecked())
616 ui->labelFeeMinimized->setText(ui->labelSmartFee->text());
617 else {
618 ui->labelFeeMinimized->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), ui->customFee->value()) +
619 ((ui->radioCustomPerKilobyte->isChecked()) ? "/kB" : ""));
620 }
621 }
622
updateMinFeeLabel()623 void SendCoinsDialog::updateMinFeeLabel()
624 {
625 if (model && model->getOptionsModel())
626 ui->checkBoxMinimumFee->setText(tr("Pay only the required fee of %1").arg(
627 BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), CWallet::GetRequiredFee(1000)) + "/kB")
628 );
629 }
630
updateSmartFeeLabel()631 void SendCoinsDialog::updateSmartFeeLabel()
632 {
633 if(!model || !model->getOptionsModel())
634 return;
635
636 int nBlocksToConfirm = defaultConfirmTarget - ui->sliderSmartFee->value();
637 int estimateFoundAtBlocks = nBlocksToConfirm;
638 CFeeRate feeRate = mempool.estimateSmartFee(nBlocksToConfirm, &estimateFoundAtBlocks);
639 if (feeRate <= CFeeRate(0)) // not enough data => minfee
640 {
641 ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(),
642 std::max(CWallet::fallbackFee.GetFeePerK(), CWallet::GetRequiredFee(1000))) + "/kB");
643 ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...)
644 ui->labelFeeEstimation->setText("");
645 }
646 else
647 {
648 ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(),
649 std::max(feeRate.GetFeePerK(), CWallet::GetRequiredFee(1000))) + "/kB");
650 ui->labelSmartFee2->hide();
651 ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", estimateFoundAtBlocks));
652 }
653
654 updateFeeMinimizedLabel();
655 }
656
657 // Coin Control: copy label "Quantity" to clipboard
coinControlClipboardQuantity()658 void SendCoinsDialog::coinControlClipboardQuantity()
659 {
660 GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
661 }
662
663 // Coin Control: copy label "Amount" to clipboard
coinControlClipboardAmount()664 void SendCoinsDialog::coinControlClipboardAmount()
665 {
666 GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
667 }
668
669 // Coin Control: copy label "Fee" to clipboard
coinControlClipboardFee()670 void SendCoinsDialog::coinControlClipboardFee()
671 {
672 GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
673 }
674
675 // Coin Control: copy label "After fee" to clipboard
coinControlClipboardAfterFee()676 void SendCoinsDialog::coinControlClipboardAfterFee()
677 {
678 GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
679 }
680
681 // Coin Control: copy label "Bytes" to clipboard
coinControlClipboardBytes()682 void SendCoinsDialog::coinControlClipboardBytes()
683 {
684 GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, ""));
685 }
686
687 // Coin Control: copy label "Priority" to clipboard
coinControlClipboardPriority()688 void SendCoinsDialog::coinControlClipboardPriority()
689 {
690 GUIUtil::setClipboard(ui->labelCoinControlPriority->text());
691 }
692
693 // Coin Control: copy label "Dust" to clipboard
coinControlClipboardLowOutput()694 void SendCoinsDialog::coinControlClipboardLowOutput()
695 {
696 GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text());
697 }
698
699 // Coin Control: copy label "Change" to clipboard
coinControlClipboardChange()700 void SendCoinsDialog::coinControlClipboardChange()
701 {
702 GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
703 }
704
705 // Coin Control: settings menu - coin control enabled/disabled by user
coinControlFeatureChanged(bool checked)706 void SendCoinsDialog::coinControlFeatureChanged(bool checked)
707 {
708 ui->frameCoinControl->setVisible(checked);
709
710 if (!checked && model) // coin control features disabled
711 CoinControlDialog::coinControl->SetNull();
712
713 coinControlUpdateLabels();
714 }
715
716 // Coin Control: button inputs -> show actual coin control dialog
coinControlButtonClicked()717 void SendCoinsDialog::coinControlButtonClicked()
718 {
719 CoinControlDialog dlg(platformStyle);
720 dlg.setModel(model);
721 dlg.exec();
722 coinControlUpdateLabels();
723 }
724
725 // Coin Control: checkbox custom change address
coinControlChangeChecked(int state)726 void SendCoinsDialog::coinControlChangeChecked(int state)
727 {
728 if (state == Qt::Unchecked)
729 {
730 CoinControlDialog::coinControl->destChange = CNoDestination();
731 ui->labelCoinControlChangeLabel->clear();
732 }
733 else
734 // use this to re-validate an already entered address
735 coinControlChangeEdited(ui->lineEditCoinControlChange->text());
736
737 ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
738 }
739
740 // Coin Control: custom change address changed
coinControlChangeEdited(const QString & text)741 void SendCoinsDialog::coinControlChangeEdited(const QString& text)
742 {
743 if (model && model->getAddressTableModel())
744 {
745 // Default to no change address until verified
746 CoinControlDialog::coinControl->destChange = CNoDestination();
747 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}");
748
749 CBitcoinAddress addr = CBitcoinAddress(text.toStdString());
750
751 if (text.isEmpty()) // Nothing entered
752 {
753 ui->labelCoinControlChangeLabel->setText("");
754 }
755 else if (!addr.IsValid()) // Invalid address
756 {
757 ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Zetacoin address"));
758 }
759 else // Valid address
760 {
761 CKeyID keyid;
762 addr.GetKeyID(keyid);
763 if (!model->havePrivKey(keyid)) // Unknown change address
764 {
765 ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
766 }
767 else // Known change address
768 {
769 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
770
771 // Query label
772 QString associatedLabel = model->getAddressTableModel()->labelForAddress(text);
773 if (!associatedLabel.isEmpty())
774 ui->labelCoinControlChangeLabel->setText(associatedLabel);
775 else
776 ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
777
778 CoinControlDialog::coinControl->destChange = addr.Get();
779 }
780 }
781 }
782 }
783
784 // Coin Control: update labels
coinControlUpdateLabels()785 void SendCoinsDialog::coinControlUpdateLabels()
786 {
787 if (!model || !model->getOptionsModel())
788 return;
789
790 if (model->getOptionsModel()->getCoinControlFeatures())
791 {
792 // enable minimum absolute fee UI controls
793 ui->radioCustomAtLeast->setVisible(true);
794
795 // only enable the feature if inputs are selected
796 ui->radioCustomAtLeast->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked() &&CoinControlDialog::coinControl->HasSelected());
797 }
798 else
799 {
800 // in case coin control is disabled (=default), hide minimum absolute fee UI controls
801 ui->radioCustomAtLeast->setVisible(false);
802 return;
803 }
804
805 // set pay amounts
806 CoinControlDialog::payAmounts.clear();
807 CoinControlDialog::fSubtractFeeFromAmount = false;
808 for(int i = 0; i < ui->entries->count(); ++i)
809 {
810 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
811 if(entry && !entry->isHidden())
812 {
813 SendCoinsRecipient rcp = entry->getValue();
814 CoinControlDialog::payAmounts.append(rcp.amount);
815 if (rcp.fSubtractFeeFromAmount)
816 CoinControlDialog::fSubtractFeeFromAmount = true;
817 }
818 }
819
820 if (CoinControlDialog::coinControl->HasSelected())
821 {
822 // actual coin control calculation
823 CoinControlDialog::updateLabels(model, this);
824
825 // show coin control stats
826 ui->labelCoinControlAutomaticallySelected->hide();
827 ui->widgetCoinControl->show();
828 }
829 else
830 {
831 // hide coin control stats
832 ui->labelCoinControlAutomaticallySelected->show();
833 ui->widgetCoinControl->hide();
834 ui->labelCoinControlInsuffFunds->hide();
835 }
836 }
837
SendConfirmationDialog(const QString & title,const QString & text,int secDelay,QWidget * parent)838 SendConfirmationDialog::SendConfirmationDialog(const QString &title, const QString &text, int secDelay,
839 QWidget *parent) :
840 QMessageBox(QMessageBox::Question, title, text, QMessageBox::Yes | QMessageBox::Cancel, parent), secDelay(secDelay)
841 {
842 setDefaultButton(QMessageBox::Cancel);
843 yesButton = button(QMessageBox::Yes);
844 updateYesButton();
845 connect(&countDownTimer, SIGNAL(timeout()), this, SLOT(countDown()));
846 }
847
exec()848 int SendConfirmationDialog::exec()
849 {
850 updateYesButton();
851 countDownTimer.start(1000);
852 return QMessageBox::exec();
853 }
854
countDown()855 void SendConfirmationDialog::countDown()
856 {
857 secDelay--;
858 updateYesButton();
859
860 if(secDelay <= 0)
861 {
862 countDownTimer.stop();
863 }
864 }
865
updateYesButton()866 void SendConfirmationDialog::updateYesButton()
867 {
868 if(secDelay > 0)
869 {
870 yesButton->setEnabled(false);
871 yesButton->setText(tr("Yes") + " (" + QString::number(secDelay) + ")");
872 }
873 else
874 {
875 yesButton->setEnabled(true);
876 yesButton->setText(tr("Yes"));
877 }
878 }
879