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