1 // Copyright (c) 2011-2014 The Bitcoin developers
2 // Distributed under the MIT/X11 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 "coincontroldialog.h"
12 #include "optionsmodel.h"
13 #include "sendcoinsentry.h"
14 #include "walletmodel.h"
17 #include "coincontrol.h"
18 #include "ui_interface.h"
20 #include <QMessageBox>
22 #include <QTextDocument>
24 SendCoinsDialog::SendCoinsDialog(QWidget *parent) :
26 ui(new Ui::SendCoinsDialog),
31 #ifdef Q_OS_MAC // Icons on push buttons are very uncommon on Mac
32 ui->addButton->setIcon(QIcon());
33 ui->clearButton->setIcon(QIcon());
34 ui->sendButton->setIcon(QIcon());
37 GUIUtil::setupAddressWidget(ui->lineEditCoinControlChange, this);
41 connect(ui->addButton, SIGNAL(clicked()), this, SLOT(addEntry()));
42 connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear()));
45 connect(ui->pushButtonCoinControl, SIGNAL(clicked()), this, SLOT(coinControlButtonClicked()));
46 connect(ui->checkBoxCoinControlChange, SIGNAL(stateChanged(int)), this, SLOT(coinControlChangeChecked(int)));
47 connect(ui->lineEditCoinControlChange, SIGNAL(textEdited(const QString &)), this, SLOT(coinControlChangeEdited(const QString &)));
49 // Coin Control: clipboard actions
50 QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this);
51 QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this);
52 QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this);
53 QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this);
54 QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this);
55 QAction *clipboardPriorityAction = new QAction(tr("Copy priority"), this);
56 QAction *clipboardLowOutputAction = new QAction(tr("Copy low output"), this);
57 QAction *clipboardChangeAction = new QAction(tr("Copy change"), this);
58 connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardQuantity()));
59 connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAmount()));
60 connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardFee()));
61 connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAfterFee()));
62 connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardBytes()));
63 connect(clipboardPriorityAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardPriority()));
64 connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardLowOutput()));
65 connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardChange()));
66 ui->labelCoinControlQuantity->addAction(clipboardQuantityAction);
67 ui->labelCoinControlAmount->addAction(clipboardAmountAction);
68 ui->labelCoinControlFee->addAction(clipboardFeeAction);
69 ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction);
70 ui->labelCoinControlBytes->addAction(clipboardBytesAction);
71 ui->labelCoinControlPriority->addAction(clipboardPriorityAction);
72 ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction);
73 ui->labelCoinControlChange->addAction(clipboardChangeAction);
75 fNewRecipientAllowed = true;
78 void SendCoinsDialog::setModel(WalletModel *model)
82 if(model && model->getOptionsModel())
84 for(int i = 0; i < ui->entries->count(); ++i)
86 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
89 entry->setModel(model);
93 setBalance(model->getBalance(), model->getUnconfirmedBalance(), model->getImmatureBalance());
94 connect(model, SIGNAL(balanceChanged(qint64, qint64, qint64)), this, SLOT(setBalance(qint64, qint64, qint64)));
95 connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
98 connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(coinControlUpdateLabels()));
99 connect(model->getOptionsModel(), SIGNAL(coinControlFeaturesChanged(bool)), this, SLOT(coinControlFeatureChanged(bool)));
100 connect(model->getOptionsModel(), SIGNAL(transactionFeeChanged(qint64)), this, SLOT(coinControlUpdateLabels()));
101 ui->frameCoinControl->setVisible(model->getOptionsModel()->getCoinControlFeatures());
102 coinControlUpdateLabels();
106 SendCoinsDialog::~SendCoinsDialog()
111 void SendCoinsDialog::on_sendButton_clicked()
113 if(!model || !model->getOptionsModel())
116 QList<SendCoinsRecipient> recipients;
119 for(int i = 0; i < ui->entries->count(); ++i)
121 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
124 if(entry->validate())
126 recipients.append(entry->getValue());
135 if(!valid || recipients.isEmpty())
140 // Format confirmation message
141 QStringList formatted;
142 foreach(const SendCoinsRecipient &rcp, recipients)
144 // generate bold amount string
145 QString amount = "<b>" + BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
146 amount.append("</b>");
147 // generate monospace address string
148 QString address = "<span style='font-family: monospace;'>" + rcp.address;
149 address.append("</span>");
151 QString recipientElement;
153 if (!rcp.paymentRequest.IsInitialized()) // normal payment
155 if(rcp.label.length() > 0) // label with address
157 recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.label));
158 recipientElement.append(QString(" (%1)").arg(address));
162 recipientElement = tr("%1 to %2").arg(amount, address);
165 else if(!rcp.authenticatedMerchant.isEmpty()) // secure payment request
167 recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.authenticatedMerchant));
169 else // insecure payment request
171 recipientElement = tr("%1 to %2").arg(amount, address);
174 formatted.append(recipientElement);
177 fNewRecipientAllowed = false;
180 WalletModel::UnlockContext ctx(model->requestUnlock());
183 // Unlock wallet was cancelled
184 fNewRecipientAllowed = true;
188 // prepare transaction for getting txFee earlier
189 WalletModelTransaction currentTransaction(recipients);
190 WalletModel::SendCoinsReturn prepareStatus;
191 if (model->getOptionsModel()->getCoinControlFeatures()) // coin control enabled
192 prepareStatus = model->prepareTransaction(currentTransaction, CoinControlDialog::coinControl);
194 prepareStatus = model->prepareTransaction(currentTransaction);
196 // process prepareStatus and on error generate message shown to user
197 processSendCoinsReturn(prepareStatus,
198 BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTransactionFee()));
200 if(prepareStatus.status != WalletModel::OK) {
201 fNewRecipientAllowed = true;
205 qint64 txFee = currentTransaction.getTransactionFee();
206 QString questionString = tr("Are you sure you want to send?");
207 questionString.append("<br /><br />%1");
211 // append fee string if a fee is required
212 questionString.append("<hr /><span style='color:#aa0000;'>");
213 questionString.append(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee));
214 questionString.append("</span> ");
215 questionString.append(tr("added as transaction fee"));
218 // add total amount in all subdivision units
219 questionString.append("<hr />");
220 qint64 totalAmount = currentTransaction.getTotalTransactionAmount() + txFee;
221 QStringList alternativeUnits;
222 foreach(BitcoinUnits::Unit u, BitcoinUnits::availableUnits())
224 if(u != model->getOptionsModel()->getDisplayUnit())
225 alternativeUnits.append(BitcoinUnits::formatWithUnit(u, totalAmount));
227 questionString.append(tr("Total Amount %1 (= %2)")
228 .arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), totalAmount))
229 .arg(alternativeUnits.join(" " + tr("or") + " ")));
231 QMessageBox::StandardButton retval = QMessageBox::question(this, tr("Confirm send coins"),
232 questionString.arg(formatted.join("<br />")),
233 QMessageBox::Yes | QMessageBox::Cancel,
234 QMessageBox::Cancel);
236 if(retval != QMessageBox::Yes)
238 fNewRecipientAllowed = true;
242 // now send the prepared transaction
243 WalletModel::SendCoinsReturn sendStatus = model->sendCoins(currentTransaction);
244 // process sendStatus and on error generate message shown to user
245 processSendCoinsReturn(sendStatus);
247 if (sendStatus.status == WalletModel::OK)
250 CoinControlDialog::coinControl->UnSelectAll();
251 coinControlUpdateLabels();
253 fNewRecipientAllowed = true;
256 void SendCoinsDialog::clear()
258 // Remove entries until only one left
259 while(ui->entries->count())
261 ui->entries->takeAt(0)->widget()->deleteLater();
265 updateTabsAndLabels();
268 void SendCoinsDialog::reject()
273 void SendCoinsDialog::accept()
278 SendCoinsEntry *SendCoinsDialog::addEntry()
280 SendCoinsEntry *entry = new SendCoinsEntry(this);
281 entry->setModel(model);
282 ui->entries->addWidget(entry);
283 connect(entry, SIGNAL(removeEntry(SendCoinsEntry*)), this, SLOT(removeEntry(SendCoinsEntry*)));
284 connect(entry, SIGNAL(payAmountChanged()), this, SLOT(coinControlUpdateLabels()));
286 updateTabsAndLabels();
288 // Focus the field, so that entry can start immediately
291 ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
292 qApp->processEvents();
293 QScrollBar* bar = ui->scrollArea->verticalScrollBar();
295 bar->setSliderPosition(bar->maximum());
299 void SendCoinsDialog::updateTabsAndLabels()
302 coinControlUpdateLabels();
305 void SendCoinsDialog::removeEntry(SendCoinsEntry* entry)
309 // If the last entry is about to be removed add an empty one
310 if (ui->entries->count() == 1)
313 entry->deleteLater();
315 updateTabsAndLabels();
318 QWidget *SendCoinsDialog::setupTabChain(QWidget *prev)
320 for(int i = 0; i < ui->entries->count(); ++i)
322 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
325 prev = entry->setupTabChain(prev);
328 QWidget::setTabOrder(prev, ui->sendButton);
329 QWidget::setTabOrder(ui->sendButton, ui->clearButton);
330 QWidget::setTabOrder(ui->clearButton, ui->addButton);
331 return ui->addButton;
334 void SendCoinsDialog::setAddress(const QString &address)
336 SendCoinsEntry *entry = 0;
337 // Replace the first entry if it is still unused
338 if(ui->entries->count() == 1)
340 SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
351 entry->setAddress(address);
354 void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv)
356 if(!fNewRecipientAllowed)
359 SendCoinsEntry *entry = 0;
360 // Replace the first entry if it is still unused
361 if(ui->entries->count() == 1)
363 SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
375 updateTabsAndLabels();
378 bool SendCoinsDialog::handlePaymentRequest(const SendCoinsRecipient &rv)
380 QString strSendCoins = tr("Send Coins");
381 if (rv.paymentRequest.IsInitialized()) {
382 // Expired payment request?
383 const payments::PaymentDetails& details = rv.paymentRequest.getDetails();
384 if (details.has_expires() && (int64_t)details.expires() < GetTime())
386 emit message(strSendCoins, tr("Payment request expired"),
387 CClientUIInterface::MSG_WARNING);
392 CBitcoinAddress address(rv.address.toStdString());
393 if (!address.IsValid()) {
394 emit message(strSendCoins, tr("Invalid payment address %1").arg(rv.address),
395 CClientUIInterface::MSG_WARNING);
404 void SendCoinsDialog::setBalance(qint64 balance, qint64 unconfirmedBalance, qint64 immatureBalance)
406 Q_UNUSED(unconfirmedBalance);
407 Q_UNUSED(immatureBalance);
409 if(model && model->getOptionsModel())
411 ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), balance));
415 void SendCoinsDialog::updateDisplayUnit()
417 setBalance(model->getBalance(), 0, 0);
420 void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg)
422 QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
423 // Default to a warning message, override if error message is needed
424 msgParams.second = CClientUIInterface::MSG_WARNING;
426 // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn.
427 // WalletModel::TransactionCommitFailed is used only in WalletModel::sendCoins()
428 // all others are used only in WalletModel::prepareTransaction()
429 switch(sendCoinsReturn.status)
431 case WalletModel::InvalidAddress:
432 msgParams.first = tr("The recipient address is not valid, please recheck.");
434 case WalletModel::InvalidAmount:
435 msgParams.first = tr("The amount to pay must be larger than 0.");
437 case WalletModel::AmountExceedsBalance:
438 msgParams.first = tr("The amount exceeds your balance.");
440 case WalletModel::AmountWithFeeExceedsBalance:
441 msgParams.first = tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg);
443 case WalletModel::DuplicateAddress:
444 msgParams.first = tr("Duplicate address found, can only send to each address once per send operation.");
446 case WalletModel::TransactionCreationFailed:
447 msgParams.first = tr("Transaction creation failed!");
448 msgParams.second = CClientUIInterface::MSG_ERROR;
450 case WalletModel::TransactionCommitFailed:
451 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.");
452 msgParams.second = CClientUIInterface::MSG_ERROR;
454 // included to prevent a compiler warning.
455 case WalletModel::OK:
460 emit message(tr("Send Coins"), msgParams.first, msgParams.second);
463 // Coin Control: copy label "Quantity" to clipboard
464 void SendCoinsDialog::coinControlClipboardQuantity()
466 GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
469 // Coin Control: copy label "Amount" to clipboard
470 void SendCoinsDialog::coinControlClipboardAmount()
472 GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
475 // Coin Control: copy label "Fee" to clipboard
476 void SendCoinsDialog::coinControlClipboardFee()
478 GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")));
481 // Coin Control: copy label "After fee" to clipboard
482 void SendCoinsDialog::coinControlClipboardAfterFee()
484 GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")));
487 // Coin Control: copy label "Bytes" to clipboard
488 void SendCoinsDialog::coinControlClipboardBytes()
490 GUIUtil::setClipboard(ui->labelCoinControlBytes->text());
493 // Coin Control: copy label "Priority" to clipboard
494 void SendCoinsDialog::coinControlClipboardPriority()
496 GUIUtil::setClipboard(ui->labelCoinControlPriority->text());
499 // Coin Control: copy label "Low output" to clipboard
500 void SendCoinsDialog::coinControlClipboardLowOutput()
502 GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text());
505 // Coin Control: copy label "Change" to clipboard
506 void SendCoinsDialog::coinControlClipboardChange()
508 GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")));
511 // Coin Control: settings menu - coin control enabled/disabled by user
512 void SendCoinsDialog::coinControlFeatureChanged(bool checked)
514 ui->frameCoinControl->setVisible(checked);
516 if (!checked && model) // coin control features disabled
517 CoinControlDialog::coinControl->SetNull();
520 coinControlUpdateLabels();
523 // Coin Control: button inputs -> show actual coin control dialog
524 void SendCoinsDialog::coinControlButtonClicked()
526 CoinControlDialog dlg;
529 coinControlUpdateLabels();
532 // Coin Control: checkbox custom change address
533 void SendCoinsDialog::coinControlChangeChecked(int state)
535 if (state == Qt::Unchecked)
537 CoinControlDialog::coinControl->destChange = CNoDestination();
538 ui->labelCoinControlChangeLabel->clear();
541 // use this to re-validate an already entered address
542 coinControlChangeEdited(ui->lineEditCoinControlChange->text());
544 ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
547 // Coin Control: custom change address changed
548 void SendCoinsDialog::coinControlChangeEdited(const QString& text)
550 if (model && model->getAddressTableModel())
552 // Default to no change address until verified
553 CoinControlDialog::coinControl->destChange = CNoDestination();
554 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}");
556 CBitcoinAddress addr = CBitcoinAddress(text.toStdString());
558 if (text.isEmpty()) // Nothing entered
560 ui->labelCoinControlChangeLabel->setText("");
562 else if (!addr.IsValid()) // Invalid address
564 ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Bitcoin address"));
566 else // Valid address
570 addr.GetKeyID(keyid);
571 if (!model->getPubKey(keyid, pubkey)) // Unknown change address
573 ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
575 else // Known change address
577 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
580 QString associatedLabel = model->getAddressTableModel()->labelForAddress(text);
581 if (!associatedLabel.isEmpty())
582 ui->labelCoinControlChangeLabel->setText(associatedLabel);
584 ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
586 CoinControlDialog::coinControl->destChange = addr.Get();
592 // Coin Control: update labels
593 void SendCoinsDialog::coinControlUpdateLabels()
595 if (!model || !model->getOptionsModel() || !model->getOptionsModel()->getCoinControlFeatures())
599 CoinControlDialog::payAmounts.clear();
600 for(int i = 0; i < ui->entries->count(); ++i)
602 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
604 CoinControlDialog::payAmounts.append(entry->getValue().amount);
607 if (CoinControlDialog::coinControl->HasSelected())
609 // actual coin control calculation
610 CoinControlDialog::updateLabels(model, this);
612 // show coin control stats
613 ui->labelCoinControlAutomaticallySelected->hide();
614 ui->widgetCoinControl->show();
618 // hide coin control stats
619 ui->labelCoinControlAutomaticallySelected->show();
620 ui->widgetCoinControl->hide();
621 ui->labelCoinControlInsuffFunds->hide();