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