coincontroldialog.cpp
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 }