BSHA3  0.17.99
P2P Blockchain, based on Bitcoin
sendcoinsdialog.cpp
Go to the documentation of this file.
1 // Copyright (c) 2011-2018 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 #if defined(HAVE_CONFIG_H)
7 #endif
8 
9 #include <qt/sendcoinsdialog.h>
10 #include <qt/forms/ui_sendcoinsdialog.h>
11 
12 #include <qt/addresstablemodel.h>
13 #include <qt/bitcoinunits.h>
14 #include <qt/clientmodel.h>
15 #include <qt/coincontroldialog.h>
16 #include <qt/guiutil.h>
17 #include <qt/optionsmodel.h>
18 #include <qt/platformstyle.h>
19 #include <qt/sendcoinsentry.h>
20 
21 #include <chainparams.h>
22 #include <interfaces/node.h>
23 #include <key_io.h>
24 #include <wallet/coincontrol.h>
25 #include <ui_interface.h>
26 #include <txmempool.h>
27 #include <policy/fees.h>
28 #include <wallet/fees.h>
29 
30 #include <QFontMetrics>
31 #include <QScrollBar>
32 #include <QSettings>
33 #include <QTextDocument>
34 
35 static const std::array<int, 9> confTargets = { {2, 4, 6, 12, 24, 48, 144, 504, 1008} };
36 int getConfTargetForIndex(int index) {
37  if (index+1 > static_cast<int>(confTargets.size())) {
38  return confTargets.back();
39  }
40  if (index < 0) {
41  return confTargets[0];
42  }
43  return confTargets[index];
44 }
45 int getIndexForConfTarget(int target) {
46  for (unsigned int i = 0; i < confTargets.size(); i++) {
47  if (confTargets[i] >= target) {
48  return i;
49  }
50  }
51  return confTargets.size() - 1;
52 }
53 
54 SendCoinsDialog::SendCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) :
55  QDialog(parent),
56  ui(new Ui::SendCoinsDialog),
57  clientModel(0),
58  model(0),
59  fNewRecipientAllowed(true),
60  fFeeMinimized(true),
61  platformStyle(_platformStyle)
62 {
63  ui->setupUi(this);
64 
65  if (!_platformStyle->getImagesOnButtons()) {
66  ui->addButton->setIcon(QIcon());
67  ui->clearButton->setIcon(QIcon());
68  ui->sendButton->setIcon(QIcon());
69  } else {
70  ui->addButton->setIcon(_platformStyle->SingleColorIcon(":/icons/add"));
71  ui->clearButton->setIcon(_platformStyle->SingleColorIcon(":/icons/remove"));
72  ui->sendButton->setIcon(_platformStyle->SingleColorIcon(":/icons/send"));
73  }
74 
75  GUIUtil::setupAddressWidget(ui->lineEditCoinControlChange, this);
76 
77  addEntry();
78 
79  connect(ui->addButton, &QPushButton::clicked, this, &SendCoinsDialog::addEntry);
80  connect(ui->clearButton, &QPushButton::clicked, this, &SendCoinsDialog::clear);
81 
82  // Coin Control
83  connect(ui->pushButtonCoinControl, &QPushButton::clicked, this, &SendCoinsDialog::coinControlButtonClicked);
84  connect(ui->checkBoxCoinControlChange, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlChangeChecked);
85  connect(ui->lineEditCoinControlChange, &QValidatedLineEdit::textEdited, this, &SendCoinsDialog::coinControlChangeEdited);
86 
87  // Coin Control: clipboard actions
88  QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this);
89  QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this);
90  QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this);
91  QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this);
92  QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this);
93  QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this);
94  QAction *clipboardChangeAction = new QAction(tr("Copy change"), this);
95  connect(clipboardQuantityAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardQuantity);
96  connect(clipboardAmountAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardAmount);
97  connect(clipboardFeeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardFee);
98  connect(clipboardAfterFeeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardAfterFee);
99  connect(clipboardBytesAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardBytes);
100  connect(clipboardLowOutputAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardLowOutput);
101  connect(clipboardChangeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardChange);
102  ui->labelCoinControlQuantity->addAction(clipboardQuantityAction);
103  ui->labelCoinControlAmount->addAction(clipboardAmountAction);
104  ui->labelCoinControlFee->addAction(clipboardFeeAction);
105  ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction);
106  ui->labelCoinControlBytes->addAction(clipboardBytesAction);
107  ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction);
108  ui->labelCoinControlChange->addAction(clipboardChangeAction);
109 
110  // init transaction fee section
111  QSettings settings;
112  if (!settings.contains("fFeeSectionMinimized"))
113  settings.setValue("fFeeSectionMinimized", true);
114  if (!settings.contains("nFeeRadio") && settings.contains("nTransactionFee") && settings.value("nTransactionFee").toLongLong() > 0) // compatibility
115  settings.setValue("nFeeRadio", 1); // custom
116  if (!settings.contains("nFeeRadio"))
117  settings.setValue("nFeeRadio", 0); // recommended
118  if (!settings.contains("nSmartFeeSliderPosition"))
119  settings.setValue("nSmartFeeSliderPosition", 0);
120  if (!settings.contains("nTransactionFee"))
121  settings.setValue("nTransactionFee", (qint64)DEFAULT_PAY_TX_FEE);
122  if (!settings.contains("fPayOnlyMinFee"))
123  settings.setValue("fPayOnlyMinFee", false);
124  ui->groupFee->setId(ui->radioSmartFee, 0);
125  ui->groupFee->setId(ui->radioCustomFee, 1);
126  ui->groupFee->button((int)std::max(0, std::min(1, settings.value("nFeeRadio").toInt())))->setChecked(true);
127  ui->customFee->setValue(settings.value("nTransactionFee").toLongLong());
128  ui->checkBoxMinimumFee->setChecked(settings.value("fPayOnlyMinFee").toBool());
129  minimizeFeeSection(settings.value("fFeeSectionMinimized").toBool());
130 }
131 
133 {
134  this->clientModel = _clientModel;
135 
136  if (_clientModel) {
138  }
139 }
140 
142 {
143  this->model = _model;
144 
145  if(_model && _model->getOptionsModel())
146  {
147  for(int i = 0; i < ui->entries->count(); ++i)
148  {
149  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
150  if(entry)
151  {
152  entry->setModel(_model);
153  }
154  }
155 
156  interfaces::WalletBalances balances = _model->wallet().getBalances();
157  setBalance(balances);
161 
162  // Coin Control
165  ui->frameCoinControl->setVisible(_model->getOptionsModel()->getCoinControlFeatures());
167 
168  // fee section
169  for (const int n : confTargets) {
170  ui->confTargetSelector->addItem(tr("%1 (%2 blocks)").arg(GUIUtil::formatNiceTimeOffset(n*Params().GetConsensus().nPowTargetSpacing)).arg(n));
171  }
172  connect(ui->confTargetSelector, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::updateSmartFeeLabel);
173  connect(ui->confTargetSelector, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::coinControlUpdateLabels);
174  connect(ui->groupFee, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::updateFeeSectionControls);
175  connect(ui->groupFee, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::coinControlUpdateLabels);
177  connect(ui->checkBoxMinimumFee, &QCheckBox::stateChanged, this, &SendCoinsDialog::setMinimumFee);
178  connect(ui->checkBoxMinimumFee, &QCheckBox::stateChanged, this, &SendCoinsDialog::updateFeeSectionControls);
179  connect(ui->checkBoxMinimumFee, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlUpdateLabels);
180  connect(ui->optInRBF, &QCheckBox::stateChanged, this, &SendCoinsDialog::updateSmartFeeLabel);
181  connect(ui->optInRBF, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlUpdateLabels);
182  ui->customFee->setSingleStep(model->wallet().getRequiredFee(1000));
186 
187  // set default rbf checkbox state
188  ui->optInRBF->setCheckState(Qt::Checked);
189 
190  // set the smartfee-sliders default value (wallets default conf.target or last stored value)
191  QSettings settings;
192  if (settings.value("nSmartFeeSliderPosition").toInt() != 0) {
193  // migrate nSmartFeeSliderPosition to nConfTarget
194  // nConfTarget is available since 0.15 (replaced nSmartFeeSliderPosition)
195  int nConfirmTarget = 25 - settings.value("nSmartFeeSliderPosition").toInt(); // 25 == old slider range
196  settings.setValue("nConfTarget", nConfirmTarget);
197  settings.remove("nSmartFeeSliderPosition");
198  }
199  if (settings.value("nConfTarget").toInt() == 0)
200  ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(model->wallet().getConfirmTarget()));
201  else
202  ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(settings.value("nConfTarget").toInt()));
203  }
204 }
205 
207 {
208  QSettings settings;
209  settings.setValue("fFeeSectionMinimized", fFeeMinimized);
210  settings.setValue("nFeeRadio", ui->groupFee->checkedId());
211  settings.setValue("nConfTarget", getConfTargetForIndex(ui->confTargetSelector->currentIndex()));
212  settings.setValue("nTransactionFee", (qint64)ui->customFee->value());
213  settings.setValue("fPayOnlyMinFee", ui->checkBoxMinimumFee->isChecked());
214 
215  delete ui;
216 }
217 
219 {
220  if(!model || !model->getOptionsModel())
221  return;
222 
223  QList<SendCoinsRecipient> recipients;
224  bool valid = true;
225 
226  for(int i = 0; i < ui->entries->count(); ++i)
227  {
228  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
229  if(entry)
230  {
231  if(entry->validate(model->node()))
232  {
233  recipients.append(entry->getValue());
234  }
235  else
236  {
237  valid = false;
238  }
239  }
240  }
241 
242  if(!valid || recipients.isEmpty())
243  {
244  return;
245  }
246 
247  fNewRecipientAllowed = false;
249  if(!ctx.isValid())
250  {
251  // Unlock wallet was cancelled
252  fNewRecipientAllowed = true;
253  return;
254  }
255 
256  // prepare transaction for getting txFee earlier
257  WalletModelTransaction currentTransaction(recipients);
258  WalletModel::SendCoinsReturn prepareStatus;
259 
260  // Always use a CCoinControl instance, use the CoinControlDialog instance if CoinControl has been enabled
261  CCoinControl ctrl;
264 
266 
267  prepareStatus = model->prepareTransaction(currentTransaction, ctrl);
268 
269  // process prepareStatus and on error generate message shown to user
270  processSendCoinsReturn(prepareStatus,
272 
273  if(prepareStatus.status != WalletModel::OK) {
274  fNewRecipientAllowed = true;
275  return;
276  }
277 
278  CAmount txFee = currentTransaction.getTransactionFee();
279 
280  // Format confirmation message
281  QStringList formatted;
282  for (const SendCoinsRecipient &rcp : currentTransaction.getRecipients())
283  {
284  // generate bold amount string with wallet name in case of multiwallet
285  QString amount = "<b>" + BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
286  if (model->isMultiwallet()) {
287  amount.append(" <u>"+tr("from wallet %1").arg(GUIUtil::HtmlEscape(model->getWalletName()))+"</u> ");
288  }
289  amount.append("</b>");
290  // generate monospace address string
291  QString address = "<span style='font-family: monospace;'>" + rcp.address;
292  address.append("</span>");
293 
294  QString recipientElement;
295  recipientElement = "<br />";
296 
297 #ifdef ENABLE_BIP70
298  if (!rcp.paymentRequest.IsInitialized()) // normal payment
299 #endif
300  {
301  if(rcp.label.length() > 0) // label with address
302  {
303  recipientElement.append(tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.label)));
304  recipientElement.append(QString(" (%1)").arg(address));
305  }
306  else // just address
307  {
308  recipientElement.append(tr("%1 to %2").arg(amount, address));
309  }
310  }
311 #ifdef ENABLE_BIP70
312  else if(!rcp.authenticatedMerchant.isEmpty()) // authenticated payment request
313  {
314  recipientElement.append(tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.authenticatedMerchant)));
315  }
316  else // unauthenticated payment request
317  {
318  recipientElement.append(tr("%1 to %2").arg(amount, address));
319  }
320 #endif
321 
322  formatted.append(recipientElement);
323  }
324 
325  QString questionString = tr("Are you sure you want to send?");
326  questionString.append("<br /><span style='font-size:10pt;'>");
327  questionString.append(tr("Please, review your transaction."));
328  questionString.append("</span><br />%1");
329 
330  if(txFee > 0)
331  {
332  // append fee string if a fee is required
333  questionString.append("<hr /><b>");
334  questionString.append(tr("Transaction fee"));
335  questionString.append("</b>");
336 
337  // append transaction size
338  questionString.append(" (" + QString::number((double)currentTransaction.getTransactionSize() / 1000) + " kB): ");
339 
340  // append transaction fee value
341  questionString.append("<span style='color:#aa0000; font-weight:bold;'>");
342  questionString.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee));
343  questionString.append("</span><br />");
344 
345  // append RBF message according to transaction's signalling
346  questionString.append("<span style='font-size:10pt; font-weight:normal;'>");
347  if (ui->optInRBF->isChecked()) {
348  questionString.append(tr("You can increase the fee later (signals Replace-By-Fee, BIP-125)."));
349  } else {
350  questionString.append(tr("Not signalling Replace-By-Fee, BIP-125."));
351  }
352  questionString.append("</span>");
353  }
354 
355  // add total amount in all subdivision units
356  questionString.append("<hr />");
357  CAmount totalAmount = currentTransaction.getTotalTransactionAmount() + txFee;
358  QStringList alternativeUnits;
360  {
361  if(u != model->getOptionsModel()->getDisplayUnit())
362  alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
363  }
364  questionString.append(QString("<b>%1</b>: <b>%2</b>").arg(tr("Total Amount"))
366  questionString.append(QString("<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>")
367  .arg(alternativeUnits.join(" " + tr("or") + " ")));
368 
369  SendConfirmationDialog confirmationDialog(tr("Confirm send coins"),
370  questionString.arg(formatted.join("<br />")), SEND_CONFIRM_DELAY, this);
371  confirmationDialog.exec();
372  QMessageBox::StandardButton retval = static_cast<QMessageBox::StandardButton>(confirmationDialog.result());
373 
374  if(retval != QMessageBox::Yes)
375  {
376  fNewRecipientAllowed = true;
377  return;
378  }
379 
380  // now send the prepared transaction
381  WalletModel::SendCoinsReturn sendStatus = model->sendCoins(currentTransaction);
382  // process sendStatus and on error generate message shown to user
383  processSendCoinsReturn(sendStatus);
384 
385  if (sendStatus.status == WalletModel::OK)
386  {
387  accept();
390  Q_EMIT coinsSent(currentTransaction.getWtx()->get().GetHash());
391  }
392  fNewRecipientAllowed = true;
393 }
394 
396 {
397  // Clear coin control settings
399  ui->checkBoxCoinControlChange->setChecked(false);
400  ui->lineEditCoinControlChange->clear();
402 
403  // Remove entries until only one left
404  while(ui->entries->count())
405  {
406  ui->entries->takeAt(0)->widget()->deleteLater();
407  }
408  addEntry();
409 
411 }
412 
414 {
415  clear();
416 }
417 
419 {
420  clear();
421 }
422 
424 {
425  SendCoinsEntry *entry = new SendCoinsEntry(platformStyle, this);
426  entry->setModel(model);
427  ui->entries->addWidget(entry);
432 
433  // Focus the field, so that entry can start immediately
434  entry->clear();
435  entry->setFocus();
436  ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
437  qApp->processEvents();
438  QScrollBar* bar = ui->scrollArea->verticalScrollBar();
439  if(bar)
440  bar->setSliderPosition(bar->maximum());
441 
443  return entry;
444 }
445 
447 {
448  setupTabChain(0);
450 }
451 
453 {
454  entry->hide();
455 
456  // If the last entry is about to be removed add an empty one
457  if (ui->entries->count() == 1)
458  addEntry();
459 
460  entry->deleteLater();
461 
463 }
464 
465 QWidget *SendCoinsDialog::setupTabChain(QWidget *prev)
466 {
467  for(int i = 0; i < ui->entries->count(); ++i)
468  {
469  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
470  if(entry)
471  {
472  prev = entry->setupTabChain(prev);
473  }
474  }
475  QWidget::setTabOrder(prev, ui->sendButton);
476  QWidget::setTabOrder(ui->sendButton, ui->clearButton);
477  QWidget::setTabOrder(ui->clearButton, ui->addButton);
478  return ui->addButton;
479 }
480 
481 void SendCoinsDialog::setAddress(const QString &address)
482 {
483  SendCoinsEntry *entry = 0;
484  // Replace the first entry if it is still unused
485  if(ui->entries->count() == 1)
486  {
487  SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
488  if(first->isClear())
489  {
490  entry = first;
491  }
492  }
493  if(!entry)
494  {
495  entry = addEntry();
496  }
497 
498  entry->setAddress(address);
499 }
500 
502 {
504  return;
505 
506  SendCoinsEntry *entry = 0;
507  // Replace the first entry if it is still unused
508  if(ui->entries->count() == 1)
509  {
510  SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
511  if(first->isClear())
512  {
513  entry = first;
514  }
515  }
516  if(!entry)
517  {
518  entry = addEntry();
519  }
520 
521  entry->setValue(rv);
523 }
524 
526 {
527  // Just paste the entry, all pre-checks
528  // are done in paymentserver.cpp.
529  pasteEntry(rv);
530  return true;
531 }
532 
534 {
535  if(model && model->getOptionsModel())
536  {
537  ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), balances.balance));
538  }
539 }
540 
542 {
544  ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
547 }
548 
549 void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg)
550 {
551  QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
552  // Default to a warning message, override if error message is needed
553  msgParams.second = CClientUIInterface::MSG_WARNING;
554 
555  // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn.
556  // WalletModel::TransactionCommitFailed is used only in WalletModel::sendCoins()
557  // all others are used only in WalletModel::prepareTransaction()
558  switch(sendCoinsReturn.status)
559  {
561  msgParams.first = tr("The recipient address is not valid. Please recheck.");
562  break;
564  msgParams.first = tr("The amount to pay must be larger than 0.");
565  break;
567  msgParams.first = tr("The amount exceeds your balance.");
568  break;
570  msgParams.first = tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg);
571  break;
573  msgParams.first = tr("Duplicate address found: addresses should only be used once each.");
574  break;
576  msgParams.first = tr("Transaction creation failed!");
577  msgParams.second = CClientUIInterface::MSG_ERROR;
578  break;
580  msgParams.first = tr("The transaction was rejected with the following reason: %1").arg(sendCoinsReturn.reasonCommitFailed);
581  msgParams.second = CClientUIInterface::MSG_ERROR;
582  break;
584  msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->node().getMaxTxFee()));
585  break;
587  msgParams.first = tr("Payment request expired.");
588  msgParams.second = CClientUIInterface::MSG_ERROR;
589  break;
590  // included to prevent a compiler warning.
591  case WalletModel::OK:
592  default:
593  return;
594  }
595 
596  Q_EMIT message(tr("Send Coins"), msgParams.first, msgParams.second);
597 }
598 
600 {
601  ui->labelFeeMinimized->setVisible(fMinimize);
602  ui->buttonChooseFee ->setVisible(fMinimize);
603  ui->buttonMinimizeFee->setVisible(!fMinimize);
604  ui->frameFeeSelection->setVisible(!fMinimize);
605  ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0);
606  fFeeMinimized = fMinimize;
607 }
608 
610 {
611  minimizeFeeSection(false);
612 }
613 
615 {
617  minimizeFeeSection(true);
618 }
619 
621 {
622  // Get CCoinControl instance if CoinControl is enabled or create a new one.
623  CCoinControl coin_control;
625  coin_control = *CoinControlDialog::coinControl();
626  }
627 
628  // Calculate available amount to send.
629  CAmount amount = model->wallet().getAvailableBalance(coin_control);
630  for (int i = 0; i < ui->entries->count(); ++i) {
631  SendCoinsEntry* e = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
632  if (e && !e->isHidden() && e != entry) {
633  amount -= e->getValue().amount;
634  }
635  }
636 
637  if (amount > 0) {
639  entry->setAmount(amount);
640  } else {
641  entry->setAmount(0);
642  }
643 }
644 
646 {
647  ui->customFee->setValue(model->wallet().getRequiredFee(1000));
648 }
649 
651 {
652  ui->confTargetSelector ->setEnabled(ui->radioSmartFee->isChecked());
653  ui->labelSmartFee ->setEnabled(ui->radioSmartFee->isChecked());
654  ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked());
655  ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked());
656  ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked());
657  ui->checkBoxMinimumFee ->setEnabled(ui->radioCustomFee->isChecked());
658  ui->labelMinFeeWarning ->setEnabled(ui->radioCustomFee->isChecked());
659  ui->labelCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked());
660  ui->customFee ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked());
661 }
662 
664 {
665  if(!model || !model->getOptionsModel())
666  return;
667 
668  if (ui->radioSmartFee->isChecked())
669  ui->labelFeeMinimized->setText(ui->labelSmartFee->text());
670  else {
671  ui->labelFeeMinimized->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), ui->customFee->value()) + "/kB");
672  }
673 }
674 
676 {
677  if (model && model->getOptionsModel())
678  ui->checkBoxMinimumFee->setText(tr("Pay only the required fee of %1").arg(
680  );
681 }
682 
684 {
685  if (ui->radioCustomFee->isChecked()) {
686  ctrl.m_feerate = CFeeRate(ui->customFee->value());
687  } else {
688  ctrl.m_feerate.reset();
689  }
690  // Avoid using global defaults when sending money from the GUI
691  // Either custom fee will be used or if not selected, the confirmation target from dropdown box
692  ctrl.m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
693  ctrl.m_signal_bip125_rbf = ui->optInRBF->isChecked();
694 }
695 
697 {
698  if(!model || !model->getOptionsModel())
699  return;
700  CCoinControl coin_control;
701  updateCoinControlState(coin_control);
702  coin_control.m_feerate.reset(); // Explicitly use only fee estimation rate for smart fee labels
703  int returned_target;
704  FeeReason reason;
705  CFeeRate feeRate = CFeeRate(model->wallet().getMinimumFee(1000, coin_control, &returned_target, &reason));
706 
707  ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), feeRate.GetFeePerK()) + "/kB");
708 
709  if (reason == FeeReason::FALLBACK) {
710  ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...)
711  ui->labelFeeEstimation->setText("");
712  ui->fallbackFeeWarningLabel->setVisible(true);
713  int lightness = ui->fallbackFeeWarningLabel->palette().color(QPalette::WindowText).lightness();
714  QColor warning_colour(255 - (lightness / 5), 176 - (lightness / 3), 48 - (lightness / 14));
715  ui->fallbackFeeWarningLabel->setStyleSheet("QLabel { color: " + warning_colour.name() + "; }");
716  ui->fallbackFeeWarningLabel->setIndent(QFontMetrics(ui->fallbackFeeWarningLabel->font()).width("x"));
717  }
718  else
719  {
720  ui->labelSmartFee2->hide();
721  ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", returned_target));
722  ui->fallbackFeeWarningLabel->setVisible(false);
723  }
724 
726 }
727 
728 // Coin Control: copy label "Quantity" to clipboard
730 {
731  GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
732 }
733 
734 // Coin Control: copy label "Amount" to clipboard
736 {
737  GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
738 }
739 
740 // Coin Control: copy label "Fee" to clipboard
742 {
743  GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
744 }
745 
746 // Coin Control: copy label "After fee" to clipboard
748 {
749  GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
750 }
751 
752 // Coin Control: copy label "Bytes" to clipboard
754 {
755  GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, ""));
756 }
757 
758 // Coin Control: copy label "Dust" to clipboard
760 {
761  GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text());
762 }
763 
764 // Coin Control: copy label "Change" to clipboard
766 {
767  GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
768 }
769 
770 // Coin Control: settings menu - coin control enabled/disabled by user
772 {
773  ui->frameCoinControl->setVisible(checked);
774 
775  if (!checked && model) // coin control features disabled
777 
779 }
780 
781 // Coin Control: button inputs -> show actual coin control dialog
783 {
785  dlg.setModel(model);
786  dlg.exec();
788 }
789 
790 // Coin Control: checkbox custom change address
792 {
793  if (state == Qt::Unchecked)
794  {
796  ui->labelCoinControlChangeLabel->clear();
797  }
798  else
799  // use this to re-validate an already entered address
800  coinControlChangeEdited(ui->lineEditCoinControlChange->text());
801 
802  ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
803 }
804 
805 // Coin Control: custom change address changed
807 {
808  if (model && model->getAddressTableModel())
809  {
810  // Default to no change address until verified
812  ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}");
813 
814  const CTxDestination dest = DecodeDestination(text.toStdString());
815 
816  if (text.isEmpty()) // Nothing entered
817  {
818  ui->labelCoinControlChangeLabel->setText("");
819  }
820  else if (!IsValidDestination(dest)) // Invalid address
821  {
822  ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid BSHA3 address"));
823  }
824  else // Valid address
825  {
826  if (!model->wallet().isSpendable(dest)) {
827  ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
828 
829  // confirmation dialog
830  QMessageBox::StandardButton btnRetVal = QMessageBox::question(this, tr("Confirm custom change address"), tr("The address you selected for change is not part of this wallet. Any or all funds in your wallet may be sent to this address. Are you sure?"),
831  QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
832 
833  if(btnRetVal == QMessageBox::Yes)
835  else
836  {
837  ui->lineEditCoinControlChange->setText("");
838  ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
839  ui->labelCoinControlChangeLabel->setText("");
840  }
841  }
842  else // Known change address
843  {
844  ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
845 
846  // Query label
847  QString associatedLabel = model->getAddressTableModel()->labelForAddress(text);
848  if (!associatedLabel.isEmpty())
849  ui->labelCoinControlChangeLabel->setText(associatedLabel);
850  else
851  ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
852 
854  }
855  }
856  }
857 }
858 
859 // Coin Control: update labels
861 {
862  if (!model || !model->getOptionsModel())
863  return;
864 
866 
867  // set pay amounts
870 
871  for(int i = 0; i < ui->entries->count(); ++i)
872  {
873  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
874  if(entry && !entry->isHidden())
875  {
876  SendCoinsRecipient rcp = entry->getValue();
878  if (rcp.fSubtractFeeFromAmount)
880  }
881  }
882 
883  if (CoinControlDialog::coinControl()->HasSelected())
884  {
885  // actual coin control calculation
887 
888  // show coin control stats
889  ui->labelCoinControlAutomaticallySelected->hide();
890  ui->widgetCoinControl->show();
891  }
892  else
893  {
894  // hide coin control stats
895  ui->labelCoinControlAutomaticallySelected->show();
896  ui->widgetCoinControl->hide();
897  ui->labelCoinControlInsuffFunds->hide();
898  }
899 }
900 
901 SendConfirmationDialog::SendConfirmationDialog(const QString &title, const QString &text, int _secDelay,
902  QWidget *parent) :
903  QMessageBox(QMessageBox::Question, title, text, QMessageBox::Yes | QMessageBox::Cancel, parent), secDelay(_secDelay)
904 {
905  setDefaultButton(QMessageBox::Cancel);
906  yesButton = button(QMessageBox::Yes);
907  updateYesButton();
908  connect(&countDownTimer, &QTimer::timeout, this, &SendConfirmationDialog::countDown);
909 }
910 
912 {
913  updateYesButton();
914  countDownTimer.start(1000);
915  return QMessageBox::exec();
916 }
917 
919 {
920  secDelay--;
921  updateYesButton();
922 
923  if(secDelay <= 0)
924  {
925  countDownTimer.stop();
926  }
927 }
928 
930 {
931  if(secDelay > 0)
932  {
933  yesButton->setEnabled(false);
934  yesButton->setText(tr("Yes") + " (" + QString::number(secDelay) + ")");
935  }
936  else
937  {
938  yesButton->setEnabled(true);
939  yesButton->setText(tr("Yes"));
940  }
941 }
void removeEntry(SendCoinsEntry *entry)
std::unique_ptr< interfaces::PendingWalletTx > & getWtx()
Unit
Bitcoin units.
Definition: bitcoinunits.h:57
interfaces::Wallet & wallet() const
Definition: walletmodel.h:219
void setValue(const SendCoinsRecipient &value)
void payAmountChanged()
void updateFeeMinimizedLabel()
constexpr CAmount DEFAULT_PAY_TX_FEE
-paytxfee default
Definition: wallet.h:48
boost::optional< unsigned int > m_confirm_target
Override the default confirmation target if set.
Definition: coincontrol.h:32
void setFocus()
void on_buttonChooseFee_clicked()
SendCoinsRecipient getValue()
UnlockContext requestUnlock()
void coinControlClipboardQuantity()
static QString formatHtmlWithUnit(int unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=separatorStandard)
Format as HTML string (with unit)
void coinControlClipboardAfterFee()
void setAddress(const QString &address)
bool IsValidDestination(const CTxDestination &dest)
Check whether a CTxDestination is a CNoDestination.
Definition: standard.cpp:324
#define SEND_CONFIRM_DELAY
SendCoinsReturn sendCoins(WalletModelTransaction &transaction)
QList< SendCoinsRecipient > getRecipients() const
void coinControlFeaturesChanged(bool)
void updateCoinControlState(CCoinControl &ctrl)
QIcon SingleColorIcon(const QString &filename) const
Colorize an icon (given filename) with the icon color.
QString HtmlEscape(const QString &str, bool fMultiLine)
Definition: guiutil.cpp:216
bool handlePaymentRequest(const SendCoinsRecipient &recipient)
AddressTableModel * getAddressTableModel()
#define ASYMP_UTF8
virtual bool isSpendable(const CTxDestination &dest)=0
Return whether wallet has private key.
A single entry in the dialog for sending bitcoins.
Coin Control Features.
Definition: coincontrol.h:16
boost::optional< CFeeRate > m_feerate
Override the wallet&#39;s m_pay_tx_fee if set.
Definition: coincontrol.h:30
int getDisplayUnit() const
Definition: optionsmodel.h:74
void coinControlFeatureChanged(bool)
QWidget * setupTabChain(QWidget *prev)
Set up the tab chain manually, as Qt messes up the tab chain by default in some cases (issue https://...
int64_t CAmount
Amount in satoshis (Can be negative)
Definition: amount.h:12
static QList< CAmount > payAmounts
QWidget * setupTabChain(QWidget *prev)
Set up the tab chain manually, as Qt messes up the tab chain by default in some cases (issue https://...
Ui::SendCoinsDialog * ui
SendCoinsEntry * addEntry()
void clear()
bool validate(interfaces::Node &node)
void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent)
Definition: guiutil.cpp:104
void setBalance(const interfaces::WalletBalances &balances)
void setAddress(const QString &address)
void coinControlClipboardChange()
Collection of wallet balances.
Definition: wallet.h:310
void useAvailableBalance(SendCoinsEntry *entry)
void setClientModel(ClientModel *clientModel)
void setClipboard(const QString &str)
Definition: guiutil.cpp:757
boost::variant< CNoDestination, CKeyID, CScriptID, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessUnknown > CTxDestination
A txout script template with a specific destination.
Definition: standard.h:123
ClientModel * clientModel
void SetNull()
Definition: coincontrol.cpp:9
CTxDestination destChange
Custom change destination, if not set an address is generated.
Definition: coincontrol.h:20
QString labelForAddress(const QString &address) const
Look up label for address in address book, if not found return empty string.
virtual CAmount getMaxTxFee()=0
Get max tx fee.
SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const CCoinControl &coinControl)
virtual CAmount getMinimumFee(unsigned int tx_bytes, const CCoinControl &coin_control, int *returned_target, FeeReason *reason)=0
Get minimum fee.
WalletModel * model
Dialog for sending bitcoins.
void coinControlChangeEdited(const QString &)
static void updateLabels(WalletModel *, QDialog *)
FeeReason
Definition: fees.h:36
void numBlocksChanged(int count, const QDateTime &blockDate, double nVerificationProgress, bool header)
QString getWalletName() const
interfaces::Node & node() const
Definition: walletmodel.h:218
void removeEntry(SendCoinsEntry *entry)
void displayUnitChanged(int unit)
Model for Bitcoin network client.
Definition: clientmodel.h:44
void coinControlUpdateLabels()
boost::optional< bool > m_signal_bip125_rbf
Override the wallet&#39;s m_signal_rbf if set.
Definition: coincontrol.h:34
void UnSelectAll()
Definition: coincontrol.h:67
void setModel(WalletModel *model)
int getConfTargetForIndex(int index)
bool getCoinControlFeatures() const
Definition: optionsmodel.h:77
void checkSubtractFeeFromAmount()
bool isClear()
Return whether the entry is still empty and unedited.
void minimizeFeeSection(bool fMinimize)
void coinControlClipboardLowOutput()
void updateFeeSectionControls()
static QList< Unit > availableUnits()
Get list of units, for drop-down box.
CTxDestination DecodeDestination(const std::string &str)
Definition: key_io.cpp:214
void subtractFeeFromAmountChanged()
void setModel(WalletModel *model)
static bool fSubtractFeeFromAmount
int getIndexForConfTarget(int target)
const CChainParams & Params()
Return the currently selected parameters.
Interface to Bitcoin wallet from Qt view code.
Definition: walletmodel.h:125
SendCoinsDialog(const PlatformStyle *platformStyle, QWidget *parent=0)
SendConfirmationDialog(const QString &title, const QString &text, int secDelay=SEND_CONFIRM_DELAY, QWidget *parent=0)
void setAmount(const CAmount &amount)
void setModel(WalletModel *model)
void processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg=QString())
Fee rate in satoshis per kilobyte: CAmount / kB.
Definition: feerate.h:19
Data model for a walletmodel transaction.
void coinControlClipboardBytes()
void useAvailableBalance(SendCoinsEntry *entry)
virtual unsigned int getConfirmTarget()=0
Get tx confirm target.
bool fSubtractFeeFromAmount
Definition: walletmodel.h:83
const PlatformStyle * platformStyle
CAmount getTotalTransactionAmount() const
QString formatNiceTimeOffset(qint64 secs)
Definition: guiutil.cpp:841
static QString formatWithUnit(int unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=separatorStandard)
Format as string (with unit)
void coinControlClipboardAmount()
bool isMultiwallet()
void on_buttonMinimizeFee_clicked()
virtual CAmount getAvailableBalance(const CCoinControl &coin_control)=0
Get available balance.
virtual WalletBalances getBalances()=0
Get balances.
void pasteEntry(const SendCoinsRecipient &rv)
QAbstractButton * yesButton
virtual CAmount getRequiredFee(unsigned int tx_bytes)=0
Get required fee.
void message(const QString &title, const QString &message, unsigned int style)
void coinsSent(const uint256 &txid)
CAmount GetFeePerK() const
Return the fee in satoshis for a size of 1000 bytes.
Definition: feerate.h:41
void balanceChanged(const interfaces::WalletBalances &balances)
bool getImagesOnButtons() const
Definition: platformstyle.h:21
void coinControlButtonClicked()
void coinControlClipboardFee()
OptionsModel * getOptionsModel()
void coinControlChangeChecked(int)
static CCoinControl * coinControl()