// Copyright (c) 2011-2013 The Bitcoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "sendcoinsdialog.h" #include "ui_sendcoinsdialog.h" #include "bitcoinunits.h" #include "guiutil.h" #include "optionsmodel.h" #include "sendcoinsentry.h" #include "walletmodel.h" #include "base58.h" #include "ui_interface.h" #include #include #include SendCoinsDialog::SendCoinsDialog(QWidget *parent) : QDialog(parent), ui(new Ui::SendCoinsDialog), model(0) { ui->setupUi(this); #ifdef Q_OS_MAC // Icons on push buttons are very uncommon on Mac ui->addButton->setIcon(QIcon()); ui->clearButton->setIcon(QIcon()); ui->sendButton->setIcon(QIcon()); #endif addEntry(); connect(ui->addButton, SIGNAL(clicked()), this, SLOT(addEntry())); connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear())); fNewRecipientAllowed = true; } void SendCoinsDialog::setModel(WalletModel *model) { this->model = model; if(model && model->getOptionsModel()) { for(int i = 0; i < ui->entries->count(); ++i) { SendCoinsEntry *entry = qobject_cast(ui->entries->itemAt(i)->widget()); if(entry) { entry->setModel(model); } } setBalance(model->getBalance(), model->getUnconfirmedBalance(), model->getImmatureBalance()); connect(model, SIGNAL(balanceChanged(qint64, qint64, qint64)), this, SLOT(setBalance(qint64, qint64, qint64))); connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit())); } } SendCoinsDialog::~SendCoinsDialog() { delete ui; } void SendCoinsDialog::on_sendButton_clicked() { if(!model || !model->getOptionsModel()) return; QList recipients; bool valid = true; for(int i = 0; i < ui->entries->count(); ++i) { SendCoinsEntry *entry = qobject_cast(ui->entries->itemAt(i)->widget()); if(entry) { if(entry->validate()) { recipients.append(entry->getValue()); } else { valid = false; } } } if(!valid || recipients.isEmpty()) { return; } // Format confirmation message QStringList formatted; foreach(const SendCoinsRecipient &rcp, recipients) { // generate bold amount string QString amount = "" + BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount); amount.append(""); // generate monospace address string QString address = "" + rcp.address; address.append(""); QString recipientElement; if (!rcp.paymentRequest.IsInitialized()) // normal payment { if(rcp.label.length() > 0) // label with address { recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.label)); recipientElement.append(QString(" (%1)").arg(address)); } else // just address { recipientElement = tr("%1 to %2").arg(amount, address); } } else if(!rcp.authenticatedMerchant.isEmpty()) // secure payment request { recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.authenticatedMerchant)); } else // insecure payment request { recipientElement = tr("%1 to %2").arg(amount, address); } formatted.append(recipientElement); } fNewRecipientAllowed = false; WalletModel::UnlockContext ctx(model->requestUnlock()); if(!ctx.isValid()) { // Unlock wallet was cancelled fNewRecipientAllowed = true; return; } // prepare transaction for getting txFee earlier WalletModelTransaction currentTransaction(recipients); WalletModel::SendCoinsReturn prepareStatus = model->prepareTransaction(currentTransaction); // process prepareStatus and on error generate message shown to user processSendCoinsReturn(prepareStatus, BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTransactionFee())); if(prepareStatus.status != WalletModel::OK) { fNewRecipientAllowed = true; return; } qint64 txFee = currentTransaction.getTransactionFee(); QString questionString = tr("Are you sure you want to send?"); questionString.append("

%1"); if(txFee > 0) { // append fee string if a fee is required questionString.append("
"); questionString.append(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee)); questionString.append(" "); questionString.append(tr("added as transaction fee")); } if(txFee > 0 || recipients.count() > 1) { // add total amount string if there are more then one recipients or a fee is required questionString.append("
"); questionString.append(tr("Total Amount %1").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTotalTransactionAmount()+txFee))); } QMessageBox::StandardButton retval = QMessageBox::question(this, tr("Confirm send coins"), questionString.arg(formatted.join("
")), QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel); if(retval != QMessageBox::Yes) { fNewRecipientAllowed = true; return; } // now send the prepared transaction WalletModel::SendCoinsReturn sendStatus = model->sendCoins(currentTransaction); // process sendStatus and on error generate message shown to user processSendCoinsReturn(sendStatus); if (sendStatus.status == WalletModel::OK) { accept(); } fNewRecipientAllowed = true; } void SendCoinsDialog::clear() { // Remove entries until only one left while(ui->entries->count()) { delete ui->entries->takeAt(0)->widget(); } addEntry(); updateRemoveEnabled(); ui->sendButton->setDefault(true); } void SendCoinsDialog::reject() { clear(); } void SendCoinsDialog::accept() { clear(); } SendCoinsEntry *SendCoinsDialog::addEntry() { SendCoinsEntry *entry = new SendCoinsEntry(this); entry->setModel(model); ui->entries->addWidget(entry); connect(entry, SIGNAL(removeEntry(SendCoinsEntry*)), this, SLOT(removeEntry(SendCoinsEntry*))); updateRemoveEnabled(); // Focus the field, so that entry can start immediately entry->clear(); entry->setFocus(); ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint()); qApp->processEvents(); QScrollBar* bar = ui->scrollArea->verticalScrollBar(); if(bar) bar->setSliderPosition(bar->maximum()); return entry; } void SendCoinsDialog::updateRemoveEnabled() { // Remove buttons are enabled as soon as there is more than one send-entry bool enabled = (ui->entries->count() > 1); for(int i = 0; i < ui->entries->count(); ++i) { SendCoinsEntry *entry = qobject_cast(ui->entries->itemAt(i)->widget()); if(entry) { entry->setRemoveEnabled(enabled); } } setupTabChain(0); } void SendCoinsDialog::removeEntry(SendCoinsEntry* entry) { delete entry; updateRemoveEnabled(); } QWidget *SendCoinsDialog::setupTabChain(QWidget *prev) { for(int i = 0; i < ui->entries->count(); ++i) { SendCoinsEntry *entry = qobject_cast(ui->entries->itemAt(i)->widget()); if(entry) { prev = entry->setupTabChain(prev); } } QWidget::setTabOrder(prev, ui->addButton); QWidget::setTabOrder(ui->addButton, ui->sendButton); return ui->sendButton; } void SendCoinsDialog::setAddress(const QString &address) { SendCoinsEntry *entry = 0; // Replace the first entry if it is still unused if(ui->entries->count() == 1) { SendCoinsEntry *first = qobject_cast(ui->entries->itemAt(0)->widget()); if(first->isClear()) { entry = first; } } if(!entry) { entry = addEntry(); } entry->setAddress(address); } void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv) { if(!fNewRecipientAllowed) return; SendCoinsEntry *entry = 0; // Replace the first entry if it is still unused if(ui->entries->count() == 1) { SendCoinsEntry *first = qobject_cast(ui->entries->itemAt(0)->widget()); if(first->isClear()) { entry = first; } } if(!entry) { entry = addEntry(); } entry->setValue(rv); } bool SendCoinsDialog::handlePaymentRequest(const SendCoinsRecipient &rv) { QString strSendCoins = tr("Send Coins"); if (rv.paymentRequest.IsInitialized()) { // Expired payment request? const payments::PaymentDetails& details = rv.paymentRequest.getDetails(); if (details.has_expires() && (int64_t)details.expires() < GetTime()) { emit message(strSendCoins, tr("Payment request expired"), CClientUIInterface::MSG_WARNING); return false; } } else { CBitcoinAddress address(rv.address.toStdString()); if (!address.IsValid()) { emit message(strSendCoins, tr("Invalid payment address %1").arg(rv.address), CClientUIInterface::MSG_WARNING); return false; } } pasteEntry(rv); return true; } void SendCoinsDialog::setBalance(qint64 balance, qint64 unconfirmedBalance, qint64 immatureBalance) { Q_UNUSED(unconfirmedBalance); Q_UNUSED(immatureBalance); if(model && model->getOptionsModel()) { ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), balance)); } } void SendCoinsDialog::updateDisplayUnit() { setBalance(model->getBalance(), 0, 0); } void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg) { QPair msgParams; // Default to a warning message, override if error message is needed msgParams.second = CClientUIInterface::MSG_WARNING; // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn. // WalletModel::TransactionCommitFailed is used only in WalletModel::sendCoins() // all others are used only in WalletModel::prepareTransaction() switch(sendCoinsReturn.status) { case WalletModel::InvalidAddress: msgParams.first = tr("The recipient address is not valid, please recheck."); break; case WalletModel::InvalidAmount: msgParams.first = tr("The amount to pay must be larger than 0."); break; case WalletModel::AmountExceedsBalance: msgParams.first = tr("The amount exceeds your balance."); break; case WalletModel::AmountWithFeeExceedsBalance: msgParams.first = tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg); break; case WalletModel::DuplicateAddress: msgParams.first = tr("Duplicate address found, can only send to each address once per send operation."); break; case WalletModel::TransactionCreationFailed: msgParams.first = tr("Transaction creation failed!"); msgParams.second = CClientUIInterface::MSG_ERROR; break; case WalletModel::TransactionCommitFailed: 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."); msgParams.second = CClientUIInterface::MSG_ERROR; break; // OK and Aborted are included to prevent a compiler warning. case WalletModel::OK: case WalletModel::Aborted: default: return; } emit message(tr("Send Coins"), msgParams.first, msgParams.second); }