coincontroldialog.cpp raw

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