coincontroldialog.cpp raw

   1  // Copyright (c) 2011-present 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 <qt/coincontroldialog.h>
   6  #include <qt/forms/ui_coincontroldialog.h>
   7  
   8  #include <qt/addresstablemodel.h>
   9  #include <qt/bitcoinunits.h>
  10  #include <qt/guiutil.h>
  11  #include <qt/optionsmodel.h>
  12  #include <qt/platformstyle.h>
  13  #include <qt/walletmodel.h>
  14  
  15  #include <interfaces/node.h>
  16  #include <key_io.h>
  17  #include <policy/policy.h>
  18  #include <wallet/coincontrol.h>
  19  #include <wallet/coinselection.h>
  20  #include <wallet/wallet.h>
  21  
  22  #include <QApplication>
  23  #include <QCheckBox>
  24  #include <QCursor>
  25  #include <QDialogButtonBox>
  26  #include <QFlags>
  27  #include <QIcon>
  28  #include <QSettings>
  29  #include <QTreeWidget>
  30  
  31  using wallet::CCoinControl;
  32  
  33  QList<CAmount> CoinControlDialog::payAmounts;
  34  bool CoinControlDialog::fSubtractFeeFromAmount = false;
  35  
  36  bool CCoinControlWidgetItem::operator<(const QTreeWidgetItem &other) const {
  37      int column = treeWidget()->sortColumn();
  38      if (column == CoinControlDialog::COLUMN_AMOUNT || column == CoinControlDialog::COLUMN_DATE || column == CoinControlDialog::COLUMN_CONFIRMATIONS)
  39          return data(column, Qt::UserRole).toLongLong() < other.data(column, Qt::UserRole).toLongLong();
  40      return QTreeWidgetItem::operator<(other);
  41  }
  42  
  43  CoinControlDialog::CoinControlDialog(CCoinControl& coin_control, WalletModel* _model, const PlatformStyle *_platformStyle, QWidget *parent) :
  44      QDialog(parent, GUIUtil::dialog_flags),
  45      ui(new Ui::CoinControlDialog),
  46      m_coin_control(coin_control),
  47      model(_model),
  48      platformStyle(_platformStyle)
  49  {
  50      ui->setupUi(this);
  51  
  52      // context menu
  53      contextMenu = new QMenu(this);
  54      contextMenu->addAction(tr("&Copy address"), this, &CoinControlDialog::copyAddress);
  55      contextMenu->addAction(tr("Copy &label"), this, &CoinControlDialog::copyLabel);
  56      contextMenu->addAction(tr("Copy &amount"), this, &CoinControlDialog::copyAmount);
  57      m_copy_transaction_outpoint_action = contextMenu->addAction(tr("Copy transaction &ID and output index"), this, &CoinControlDialog::copyTransactionOutpoint);
  58      contextMenu->addSeparator();
  59      lockAction = contextMenu->addAction(tr("L&ock unspent"), this, &CoinControlDialog::lockCoin);
  60      unlockAction = contextMenu->addAction(tr("&Unlock unspent"), this, &CoinControlDialog::unlockCoin);
  61      connect(ui->treeWidget, &QWidget::customContextMenuRequested, this, &CoinControlDialog::showMenu);
  62  
  63      // clipboard actions
  64      QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this);
  65      QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this);
  66      QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this);
  67      QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this);
  68      QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this);
  69      QAction *clipboardChangeAction = new QAction(tr("Copy change"), this);
  70  
  71      connect(clipboardQuantityAction, &QAction::triggered, this, &CoinControlDialog::clipboardQuantity);
  72      connect(clipboardAmountAction, &QAction::triggered, this, &CoinControlDialog::clipboardAmount);
  73      connect(clipboardFeeAction, &QAction::triggered, this, &CoinControlDialog::clipboardFee);
  74      connect(clipboardAfterFeeAction, &QAction::triggered, this, &CoinControlDialog::clipboardAfterFee);
  75      connect(clipboardBytesAction, &QAction::triggered, this, &CoinControlDialog::clipboardBytes);
  76      connect(clipboardChangeAction, &QAction::triggered, this, &CoinControlDialog::clipboardChange);
  77  
  78      ui->labelCoinControlQuantity->addAction(clipboardQuantityAction);
  79      ui->labelCoinControlAmount->addAction(clipboardAmountAction);
  80      ui->labelCoinControlFee->addAction(clipboardFeeAction);
  81      ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction);
  82      ui->labelCoinControlBytes->addAction(clipboardBytesAction);
  83      ui->labelCoinControlChange->addAction(clipboardChangeAction);
  84  
  85      // toggle tree/list mode
  86      connect(ui->radioTreeMode, &QRadioButton::toggled, this, &CoinControlDialog::radioTreeMode);
  87      connect(ui->radioListMode, &QRadioButton::toggled, this, &CoinControlDialog::radioListMode);
  88  
  89      // click on checkbox
  90      connect(ui->treeWidget, &QTreeWidget::itemChanged, this, &CoinControlDialog::viewItemChanged);
  91  
  92      // click on header
  93      ui->treeWidget->header()->setSectionsClickable(true);
  94      connect(ui->treeWidget->header(), &QHeaderView::sectionClicked, this, &CoinControlDialog::headerSectionClicked);
  95  
  96      // ok button
  97      connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &CoinControlDialog::buttonBoxClicked);
  98  
  99      // (un)select all
 100      connect(ui->pushButtonSelectAll, &QPushButton::clicked, this, &CoinControlDialog::buttonSelectAllClicked);
 101  
 102      ui->treeWidget->setColumnWidth(COLUMN_CHECKBOX, 84);
 103      ui->treeWidget->setColumnWidth(COLUMN_AMOUNT, 110);
 104      ui->treeWidget->setColumnWidth(COLUMN_LABEL, 190);
 105      ui->treeWidget->setColumnWidth(COLUMN_ADDRESS, 320);
 106      ui->treeWidget->setColumnWidth(COLUMN_DATE, 130);
 107      ui->treeWidget->setColumnWidth(COLUMN_CONFIRMATIONS, 110);
 108  
 109      // default view is sorted by amount desc
 110      sortView(COLUMN_AMOUNT, Qt::DescendingOrder);
 111  
 112      // restore list mode and sortorder as a convenience feature
 113      QSettings settings;
 114      if (settings.contains("nCoinControlMode") && !settings.value("nCoinControlMode").toBool())
 115          ui->radioTreeMode->click();
 116      if (settings.contains("nCoinControlSortColumn") && settings.contains("nCoinControlSortOrder"))
 117          sortView(settings.value("nCoinControlSortColumn").toInt(), (static_cast<Qt::SortOrder>(settings.value("nCoinControlSortOrder").toInt())));
 118  
 119      GUIUtil::handleCloseWindowShortcut(this);
 120  
 121      if(_model->getOptionsModel() && _model->getAddressTableModel())
 122      {
 123          updateView();
 124          updateLabelLocked();
 125          CoinControlDialog::updateLabels(m_coin_control, _model, this);
 126      }
 127  }
 128  
 129  CoinControlDialog::~CoinControlDialog()
 130  {
 131      QSettings settings;
 132      settings.setValue("nCoinControlMode", ui->radioListMode->isChecked());
 133      settings.setValue("nCoinControlSortColumn", sortColumn);
 134      settings.setValue("nCoinControlSortOrder", (int)sortOrder);
 135  
 136      delete ui;
 137  }
 138  
 139  // ok button
 140  void CoinControlDialog::buttonBoxClicked(QAbstractButton* button)
 141  {
 142      if (ui->buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole)
 143          done(QDialog::Accepted); // closes the dialog
 144  }
 145  
 146  // (un)select all
 147  void CoinControlDialog::buttonSelectAllClicked()
 148  {
 149      Qt::CheckState state = Qt::Checked;
 150      for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++)
 151      {
 152          if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != Qt::Unchecked)
 153          {
 154              state = Qt::Unchecked;
 155              break;
 156          }
 157      }
 158      ui->treeWidget->setEnabled(false);
 159      for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++)
 160              if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != state)
 161                  ui->treeWidget->topLevelItem(i)->setCheckState(COLUMN_CHECKBOX, state);
 162      ui->treeWidget->setEnabled(true);
 163      if (state == Qt::Unchecked)
 164          m_coin_control.UnSelectAll(); // just to be sure
 165      CoinControlDialog::updateLabels(m_coin_control, model, this);
 166  }
 167  
 168  // context menu
 169  void CoinControlDialog::showMenu(const QPoint &point)
 170  {
 171      QTreeWidgetItem *item = ui->treeWidget->itemAt(point);
 172      if(item)
 173      {
 174          contextMenuItem = item;
 175  
 176          // disable some items (like Copy Transaction ID, lock, unlock) for tree roots in context menu
 177          auto txid{Txid::FromHex(item->data(COLUMN_ADDRESS, TxHashRole).toString().toStdString())};
 178          if (txid) { // a valid txid means this is a child node, and not a parent node in tree mode
 179              m_copy_transaction_outpoint_action->setEnabled(true);
 180              if (model->wallet().isLockedCoin(COutPoint(*txid, item->data(COLUMN_ADDRESS, VOutRole).toUInt()))) {
 181                  lockAction->setEnabled(false);
 182                  unlockAction->setEnabled(true);
 183              } else {
 184                  lockAction->setEnabled(true);
 185                  unlockAction->setEnabled(false);
 186              }
 187          } else { // this means click on parent node in tree mode -> disable all
 188              m_copy_transaction_outpoint_action->setEnabled(false);
 189              lockAction->setEnabled(false);
 190              unlockAction->setEnabled(false);
 191          }
 192  
 193          // show context menu
 194          contextMenu->exec(QCursor::pos());
 195      }
 196  }
 197  
 198  // context menu action: copy amount
 199  void CoinControlDialog::copyAmount()
 200  {
 201      GUIUtil::setClipboard(BitcoinUnits::removeSpaces(contextMenuItem->text(COLUMN_AMOUNT)));
 202  }
 203  
 204  // context menu action: copy label
 205  void CoinControlDialog::copyLabel()
 206  {
 207      if (ui->radioTreeMode->isChecked() && contextMenuItem->text(COLUMN_LABEL).length() == 0 && contextMenuItem->parent())
 208          GUIUtil::setClipboard(contextMenuItem->parent()->text(COLUMN_LABEL));
 209      else
 210          GUIUtil::setClipboard(contextMenuItem->text(COLUMN_LABEL));
 211  }
 212  
 213  // context menu action: copy address
 214  void CoinControlDialog::copyAddress()
 215  {
 216      if (ui->radioTreeMode->isChecked() && contextMenuItem->text(COLUMN_ADDRESS).length() == 0 && contextMenuItem->parent())
 217          GUIUtil::setClipboard(contextMenuItem->parent()->text(COLUMN_ADDRESS));
 218      else
 219          GUIUtil::setClipboard(contextMenuItem->text(COLUMN_ADDRESS));
 220  }
 221  
 222  // context menu action: copy transaction id and vout index
 223  void CoinControlDialog::copyTransactionOutpoint()
 224  {
 225      const QString address = contextMenuItem->data(COLUMN_ADDRESS, TxHashRole).toString();
 226      const QString vout = contextMenuItem->data(COLUMN_ADDRESS, VOutRole).toString();
 227      const QString outpoint = QString("%1:%2").arg(address).arg(vout);
 228  
 229      GUIUtil::setClipboard(outpoint);
 230  }
 231  
 232  // context menu action: lock coin
 233  void CoinControlDialog::lockCoin()
 234  {
 235      if (contextMenuItem->checkState(COLUMN_CHECKBOX) == Qt::Checked)
 236          contextMenuItem->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked);
 237  
 238      COutPoint outpt(Txid::FromHex(contextMenuItem->data(COLUMN_ADDRESS, TxHashRole).toString().toStdString()).value(), contextMenuItem->data(COLUMN_ADDRESS, VOutRole).toUInt());
 239      model->wallet().lockCoin(outpt, /* write_to_db = */ true);
 240      contextMenuItem->setDisabled(true);
 241      contextMenuItem->setIcon(COLUMN_CHECKBOX, platformStyle->SingleColorIcon(":/icons/lock_closed"));
 242      updateLabelLocked();
 243  }
 244  
 245  // context menu action: unlock coin
 246  void CoinControlDialog::unlockCoin()
 247  {
 248      COutPoint outpt(Txid::FromHex(contextMenuItem->data(COLUMN_ADDRESS, TxHashRole).toString().toStdString()).value(), contextMenuItem->data(COLUMN_ADDRESS, VOutRole).toUInt());
 249      model->wallet().unlockCoin(outpt);
 250      contextMenuItem->setDisabled(false);
 251      contextMenuItem->setIcon(COLUMN_CHECKBOX, QIcon());
 252      updateLabelLocked();
 253  }
 254  
 255  // copy label "Quantity" to clipboard
 256  void CoinControlDialog::clipboardQuantity()
 257  {
 258      GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
 259  }
 260  
 261  // copy label "Amount" to clipboard
 262  void CoinControlDialog::clipboardAmount()
 263  {
 264      GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
 265  }
 266  
 267  // copy label "Fee" to clipboard
 268  void CoinControlDialog::clipboardFee()
 269  {
 270      GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
 271  }
 272  
 273  // copy label "After fee" to clipboard
 274  void CoinControlDialog::clipboardAfterFee()
 275  {
 276      GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
 277  }
 278  
 279  // copy label "Bytes" to clipboard
 280  void CoinControlDialog::clipboardBytes()
 281  {
 282      GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, ""));
 283  }
 284  
 285  // copy label "Change" to clipboard
 286  void CoinControlDialog::clipboardChange()
 287  {
 288      GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
 289  }
 290  
 291  // treeview: sort
 292  void CoinControlDialog::sortView(int column, Qt::SortOrder order)
 293  {
 294      sortColumn = column;
 295      sortOrder = order;
 296      ui->treeWidget->sortItems(column, order);
 297      ui->treeWidget->header()->setSortIndicator(sortColumn, sortOrder);
 298  }
 299  
 300  // treeview: clicked on header
 301  void CoinControlDialog::headerSectionClicked(int logicalIndex)
 302  {
 303      if (logicalIndex == COLUMN_CHECKBOX) // click on most left column -> do nothing
 304      {
 305          ui->treeWidget->header()->setSortIndicator(sortColumn, sortOrder);
 306      }
 307      else
 308      {
 309          if (sortColumn == logicalIndex)
 310              sortOrder = ((sortOrder == Qt::AscendingOrder) ? Qt::DescendingOrder : Qt::AscendingOrder);
 311          else
 312          {
 313              sortColumn = logicalIndex;
 314              sortOrder = ((sortColumn == COLUMN_LABEL || sortColumn == COLUMN_ADDRESS) ? Qt::AscendingOrder : Qt::DescendingOrder); // if label or address then default => asc, else default => desc
 315          }
 316  
 317          sortView(sortColumn, sortOrder);
 318      }
 319  }
 320  
 321  // toggle tree mode
 322  void CoinControlDialog::radioTreeMode(bool checked)
 323  {
 324      if (checked && model)
 325          updateView();
 326  }
 327  
 328  // toggle list mode
 329  void CoinControlDialog::radioListMode(bool checked)
 330  {
 331      if (checked && model)
 332          updateView();
 333  }
 334  
 335  // checkbox clicked by user
 336  void CoinControlDialog::viewItemChanged(QTreeWidgetItem* item, int column)
 337  {
 338      if (column != COLUMN_CHECKBOX) return;
 339      auto txid{Txid::FromHex(item->data(COLUMN_ADDRESS, TxHashRole).toString().toStdString())};
 340      if (txid) { // a valid txid means this is a child node, and not a parent node in tree mode
 341          COutPoint outpt(*txid, item->data(COLUMN_ADDRESS, VOutRole).toUInt());
 342  
 343          if (item->checkState(COLUMN_CHECKBOX) == Qt::Unchecked)
 344              m_coin_control.UnSelect(outpt);
 345          else if (item->isDisabled()) // locked (this happens if "check all" through parent node)
 346              item->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked);
 347          else
 348              m_coin_control.Select(outpt);
 349  
 350          // selection changed -> update labels
 351          if (ui->treeWidget->isEnabled()) // do not update on every click for (un)select all
 352              CoinControlDialog::updateLabels(m_coin_control, model, this);
 353      }
 354  }
 355  
 356  // shows count of locked unspent outputs
 357  void CoinControlDialog::updateLabelLocked()
 358  {
 359      std::vector<COutPoint> vOutpts;
 360      model->wallet().listLockedCoins(vOutpts);
 361      if (vOutpts.size() > 0)
 362      {
 363         ui->labelLocked->setText(tr("(%1 locked)").arg(vOutpts.size()));
 364         ui->labelLocked->setVisible(true);
 365      }
 366      else ui->labelLocked->setVisible(false);
 367  }
 368  
 369  void CoinControlDialog::updateLabels(CCoinControl& m_coin_control, WalletModel *model, QDialog* dialog)
 370  {
 371      if (!model)
 372          return;
 373  
 374      // nPayAmount
 375      CAmount nPayAmount = 0;
 376      for (const CAmount &amount : CoinControlDialog::payAmounts) {
 377          nPayAmount += amount;
 378      }
 379  
 380      CAmount nAmount             = 0;
 381      CAmount nPayFee             = 0;
 382      CAmount nAfterFee           = 0;
 383      CAmount nChange             = 0;
 384      unsigned int nBytes         = 0;
 385      unsigned int nBytesInputs   = 0;
 386      unsigned int nQuantity      = 0;
 387      bool fWitness               = false;
 388  
 389      auto vCoinControl{m_coin_control.ListSelected()};
 390  
 391      size_t i = 0;
 392      for (const auto& out : model->wallet().getCoins(vCoinControl)) {
 393          if (out.depth_in_main_chain < 0) continue;
 394  
 395          // unselect already spent, very unlikely scenario, this could happen
 396          // when selected are spent elsewhere, like rpc or another computer
 397          const COutPoint& outpt = vCoinControl[i++];
 398          if (out.is_spent)
 399          {
 400              m_coin_control.UnSelect(outpt);
 401              continue;
 402          }
 403  
 404          // Quantity
 405          nQuantity++;
 406  
 407          // Amount
 408          nAmount += out.txout.nValue;
 409  
 410          // Bytes
 411          CTxDestination address;
 412          int witnessversion = 0;
 413          std::vector<unsigned char> witnessprogram;
 414          if (out.txout.scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram))
 415          {
 416              // add input skeleton bytes (outpoint, scriptSig size, nSequence)
 417              nBytesInputs += (32 + 4 + 1 + 4);
 418  
 419              if (witnessversion == 0) { // P2WPKH
 420                  // 1 WU (witness item count) + 72 WU (ECDSA signature with len byte) + 34 WU (pubkey with len byte)
 421                  nBytesInputs += 107 / WITNESS_SCALE_FACTOR;
 422              } else if (witnessversion == 1) { // P2TR key-path spend
 423                  // 1 WU (witness item count) + 65 WU (Schnorr signature with len byte)
 424                  nBytesInputs += 66 / WITNESS_SCALE_FACTOR;
 425              } else {
 426                  // not supported, should be unreachable
 427                  throw std::runtime_error("Trying to spend future segwit version script");
 428              }
 429              fWitness = true;
 430          }
 431          else if(ExtractDestination(out.txout.scriptPubKey, address))
 432          {
 433              CPubKey pubkey;
 434              PKHash* pkhash = std::get_if<PKHash>(&address);
 435              if (pkhash && model->wallet().getPubKey(out.txout.scriptPubKey, ToKeyID(*pkhash), pubkey))
 436              {
 437                  nBytesInputs += (pubkey.IsCompressed() ? 148 : 180);
 438              }
 439              else
 440                  nBytesInputs += 148; // in all error cases, simply assume 148 here
 441          }
 442          else nBytesInputs += 148;
 443      }
 444  
 445      // calculation
 446      if (nQuantity > 0)
 447      {
 448          // Bytes
 449          nBytes = nBytesInputs + ((CoinControlDialog::payAmounts.size() > 0 ? CoinControlDialog::payAmounts.size() + 1 : 2) * 34) + 10; // always assume +1 output for change here
 450          if (fWitness)
 451          {
 452              // there is some fudging in these numbers related to the actual virtual transaction size calculation that will keep this estimate from being exact.
 453              // usually, the result will be an overestimate within a couple of satoshis so that the confirmation dialog ends up displaying a slightly smaller fee.
 454              // also, the witness stack size value is a variable sized integer. usually, the number of stack items will be well under the single byte var int limit.
 455              nBytes += 2; // account for the serialized marker and flag bytes
 456              nBytes += nQuantity; // account for the witness byte that holds the number of stack items for each input.
 457          }
 458  
 459          // in the subtract fee from amount case, we can tell if zero change already and subtract the bytes, so that fee calculation afterwards is accurate
 460          if (CoinControlDialog::fSubtractFeeFromAmount)
 461              if (nAmount - nPayAmount == 0)
 462                  nBytes -= 34;
 463  
 464          // Fee
 465          nPayFee = model->wallet().getMinimumFee(nBytes, m_coin_control, /*returned_target=*/nullptr, /*reason=*/nullptr);
 466  
 467          if (nPayAmount > 0)
 468          {
 469              nChange = nAmount - nPayAmount;
 470              if (!CoinControlDialog::fSubtractFeeFromAmount)
 471                  nChange -= nPayFee;
 472  
 473              if (nChange > 0) {
 474                  // Assumes a p2pkh script size
 475                  CTxOut txout(nChange, CScript() << std::vector<unsigned char>(24, 0));
 476                  // Never create dust outputs; if we would, just add the dust to the fee.
 477                  if (IsDust(txout, model->node().getDustRelayFee()))
 478                  {
 479                      nPayFee += nChange;
 480                      nChange = 0;
 481                      if (CoinControlDialog::fSubtractFeeFromAmount)
 482                          nBytes -= 34; // we didn't detect lack of change above
 483                  }
 484              }
 485  
 486              if (nChange == 0 && !CoinControlDialog::fSubtractFeeFromAmount)
 487                  nBytes -= 34;
 488          }
 489  
 490          // after fee
 491          nAfterFee = std::max<CAmount>(nAmount - nPayFee, 0);
 492      }
 493  
 494      // actually update labels
 495      BitcoinUnit nDisplayUnit = BitcoinUnit::BTC;
 496      if (model && model->getOptionsModel())
 497          nDisplayUnit = model->getOptionsModel()->getDisplayUnit();
 498  
 499      QLabel *l1 = dialog->findChild<QLabel *>("labelCoinControlQuantity");
 500      QLabel *l2 = dialog->findChild<QLabel *>("labelCoinControlAmount");
 501      QLabel *l3 = dialog->findChild<QLabel *>("labelCoinControlFee");
 502      QLabel *l4 = dialog->findChild<QLabel *>("labelCoinControlAfterFee");
 503      QLabel *l5 = dialog->findChild<QLabel *>("labelCoinControlBytes");
 504      QLabel *l8 = dialog->findChild<QLabel *>("labelCoinControlChange");
 505  
 506      // enable/disable "change"
 507      dialog->findChild<QLabel *>("labelCoinControlChangeText")   ->setEnabled(nPayAmount > 0);
 508      dialog->findChild<QLabel *>("labelCoinControlChange")       ->setEnabled(nPayAmount > 0);
 509  
 510      // stats
 511      l1->setText(QString::number(nQuantity));                                 // Quantity
 512      l2->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nAmount));        // Amount
 513      l3->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nPayFee));        // Fee
 514      l4->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nAfterFee));      // After Fee
 515      l5->setText(((nBytes > 0) ? ASYMP_UTF8 : "") + QString::number(nBytes));        // Bytes
 516      l8->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nChange));        // Change
 517      if (nPayFee > 0)
 518      {
 519          l3->setText(ASYMP_UTF8 + l3->text());
 520          l4->setText(ASYMP_UTF8 + l4->text());
 521          if (nChange > 0 && !CoinControlDialog::fSubtractFeeFromAmount)
 522              l8->setText(ASYMP_UTF8 + l8->text());
 523      }
 524  
 525      // how many satoshis the estimated fee can vary per byte we guess wrong
 526      double dFeeVary = (nBytes != 0) ? (double)nPayFee / nBytes : 0;
 527  
 528      QString toolTip4 = tr("Can vary +/- %1 satoshi(s) per input.").arg(dFeeVary);
 529  
 530      l3->setToolTip(toolTip4);
 531      l4->setToolTip(toolTip4);
 532      l8->setToolTip(toolTip4);
 533      dialog->findChild<QLabel *>("labelCoinControlFeeText")      ->setToolTip(l3->toolTip());
 534      dialog->findChild<QLabel *>("labelCoinControlAfterFeeText") ->setToolTip(l4->toolTip());
 535      dialog->findChild<QLabel *>("labelCoinControlBytesText")    ->setToolTip(l5->toolTip());
 536      dialog->findChild<QLabel *>("labelCoinControlChangeText")   ->setToolTip(l8->toolTip());
 537  
 538      // Insufficient funds
 539      QLabel *label = dialog->findChild<QLabel *>("labelCoinControlInsuffFunds");
 540      if (label)
 541          label->setVisible(nChange < 0);
 542  }
 543  
 544  void CoinControlDialog::changeEvent(QEvent* e)
 545  {
 546      if (e->type() == QEvent::PaletteChange) {
 547          updateView();
 548      }
 549  
 550      QDialog::changeEvent(e);
 551  }
 552  
 553  void CoinControlDialog::updateView()
 554  {
 555      if (!model || !model->getOptionsModel() || !model->getAddressTableModel())
 556          return;
 557  
 558      bool treeMode = ui->radioTreeMode->isChecked();
 559  
 560      ui->treeWidget->clear();
 561      ui->treeWidget->setEnabled(false); // performance, otherwise updateLabels would be called for every checked checkbox
 562      ui->treeWidget->setAlternatingRowColors(!treeMode);
 563      QFlags<Qt::ItemFlag> flgCheckbox = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
 564      QFlags<Qt::ItemFlag> flgTristate = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsAutoTristate;
 565  
 566      BitcoinUnit nDisplayUnit = model->getOptionsModel()->getDisplayUnit();
 567  
 568      for (const auto& coins : model->wallet().listCoins()) {
 569          CCoinControlWidgetItem* itemWalletAddress{nullptr};
 570          QString sWalletAddress = QString::fromStdString(EncodeDestination(coins.first));
 571          QString sWalletLabel = model->getAddressTableModel()->labelForAddress(sWalletAddress);
 572          if (sWalletLabel.isEmpty())
 573              sWalletLabel = tr("(no label)");
 574  
 575          if (treeMode)
 576          {
 577              // wallet address
 578              itemWalletAddress = new CCoinControlWidgetItem(ui->treeWidget);
 579  
 580              itemWalletAddress->setFlags(flgTristate);
 581              itemWalletAddress->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked);
 582  
 583              // label
 584              itemWalletAddress->setText(COLUMN_LABEL, sWalletLabel);
 585  
 586              // address
 587              itemWalletAddress->setText(COLUMN_ADDRESS, sWalletAddress);
 588          }
 589  
 590          CAmount nSum = 0;
 591          int nChildren = 0;
 592          for (const auto& outpair : coins.second) {
 593              const COutPoint& output = std::get<0>(outpair);
 594              const interfaces::WalletTxOut& out = std::get<1>(outpair);
 595              nSum += out.txout.nValue;
 596              nChildren++;
 597  
 598              CCoinControlWidgetItem *itemOutput;
 599              if (treeMode)    itemOutput = new CCoinControlWidgetItem(itemWalletAddress);
 600              else             itemOutput = new CCoinControlWidgetItem(ui->treeWidget);
 601              itemOutput->setFlags(flgCheckbox);
 602              itemOutput->setCheckState(COLUMN_CHECKBOX,Qt::Unchecked);
 603  
 604              // address
 605              CTxDestination outputAddress;
 606              QString sAddress = "";
 607              if(ExtractDestination(out.txout.scriptPubKey, outputAddress))
 608              {
 609                  sAddress = QString::fromStdString(EncodeDestination(outputAddress));
 610  
 611                  // if listMode or change => show bitcoin address. In tree mode, address is not shown again for direct wallet address outputs
 612                  if (!treeMode || (!(sAddress == sWalletAddress)))
 613                      itemOutput->setText(COLUMN_ADDRESS, sAddress);
 614              }
 615  
 616              // label
 617              if (!(sAddress == sWalletAddress)) // change
 618              {
 619                  // tooltip from where the change comes from
 620                  itemOutput->setToolTip(COLUMN_LABEL, tr("change from %1 (%2)").arg(sWalletLabel).arg(sWalletAddress));
 621                  itemOutput->setText(COLUMN_LABEL, tr("(change)"));
 622              }
 623              else if (!treeMode)
 624              {
 625                  QString sLabel = model->getAddressTableModel()->labelForAddress(sAddress);
 626                  if (sLabel.isEmpty())
 627                      sLabel = tr("(no label)");
 628                  itemOutput->setText(COLUMN_LABEL, sLabel);
 629              }
 630  
 631              // amount
 632              itemOutput->setText(COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, out.txout.nValue));
 633              itemOutput->setData(COLUMN_AMOUNT, Qt::UserRole, QVariant((qlonglong)out.txout.nValue)); // padding so that sorting works correctly
 634  
 635              // date
 636              itemOutput->setText(COLUMN_DATE, GUIUtil::dateTimeStr(out.time));
 637              itemOutput->setData(COLUMN_DATE, Qt::UserRole, QVariant((qlonglong)out.time));
 638  
 639              // confirmations
 640              itemOutput->setText(COLUMN_CONFIRMATIONS, QString::number(out.depth_in_main_chain));
 641              itemOutput->setData(COLUMN_CONFIRMATIONS, Qt::UserRole, QVariant((qlonglong)out.depth_in_main_chain));
 642  
 643              // transaction hash
 644              itemOutput->setData(COLUMN_ADDRESS, TxHashRole, QString::fromStdString(output.hash.GetHex()));
 645  
 646              // vout index
 647              itemOutput->setData(COLUMN_ADDRESS, VOutRole, output.n);
 648  
 649               // disable locked coins
 650              if (model->wallet().isLockedCoin(output))
 651              {
 652                  m_coin_control.UnSelect(output); // just to be sure
 653                  itemOutput->setDisabled(true);
 654                  itemOutput->setIcon(COLUMN_CHECKBOX, platformStyle->SingleColorIcon(":/icons/lock_closed"));
 655              }
 656  
 657              // set checkbox
 658              if (m_coin_control.IsSelected(output))
 659                  itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Checked);
 660          }
 661  
 662          // amount
 663          if (treeMode)
 664          {
 665              itemWalletAddress->setText(COLUMN_CHECKBOX, "(" + QString::number(nChildren) + ")");
 666              itemWalletAddress->setText(COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, nSum));
 667              itemWalletAddress->setData(COLUMN_AMOUNT, Qt::UserRole, QVariant((qlonglong)nSum));
 668          }
 669      }
 670  
 671      // expand all partially selected
 672      if (treeMode)
 673      {
 674          for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++)
 675              if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked)
 676                  ui->treeWidget->topLevelItem(i)->setExpanded(true);
 677      }
 678  
 679      // sort view
 680      sortView(sortColumn, sortOrder);
 681      ui->treeWidget->setEnabled(true);
 682  }
 683