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