Skip to content

Commit

Permalink
qt: Finetune CoinControlDialog + bitcoin#14828 (dashpay#3701)
Browse files Browse the repository at this point in the history
* qt: Add min-height for CoinControlTreeWidget#treeWidget::item

The rows resize without it if they get locked and the lock icon appears
besides the checkbox. Looks weird.. and especially if you press the lock
all button its just not nice.

* qt: Set background transparency for CoinControl item::hover

* Merge bitcoin#14828: qt: Remove hidden columns in coin control dialog

1c28feb qt: Remove hidden columns in coin control dialog (João Barbosa)

Pull request description:

  Instead of having hidden columns, store the data in specific roles.

  Overlaps with bitcoin#14817, fixes bitcoin#11811.

Tree-SHA512: e86e9ca426b9146ac28997ca1920dbae6cc4e2e494ff94fe131d605cd6c013183fc5de10036c886a4d6dcae497ac4067de3791be0ef9c88f7ce9f57f7bd97422

* qt: Add border-bottom for tree items in CoinControl

* qt: Stretch address column in CoinControlDialog

* Adjust column width for a couple of columns

* qt: Hide PrivateSend rounds column for normal Send tab's CoinControl

* qt: Hide unrelated coins in CoinControl based on active mode. Still allow to show them.

* qt: Hide empty top level items in CoinControlDialog's tree mode

* qt: Hide tree/list radio buttons and default to list for PrivateSend

* qt: Hide address/label column in CoinControl for PrivateSend

* qt: Remove obsolete empty columns

* qt: Rename column "PS Rounds" to "Mixing Rounds"

* qt: Move border-bottom in already existing css selector

* Reveal all PS related coins in coincontrol while in PS mode, not only ones with rounds>=1

Also tweak button text

* qt: Only moving a statement a bit

* qt: Hide the "hideButton" in CoinControlDialog if PrivatSend is disabled

And make it default to show all coins in that case..

Co-authored-by: Jonas Schnelli <dev@jonasschnelli.ch>
Co-authored-by: UdjinM6 <UdjinM6@users.noreply.github.com>
  • Loading branch information
3 people authored and PastaPastaPasta committed Sep 24, 2020
1 parent e405f55 commit f4e3bf9
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 113 deletions.
240 changes: 152 additions & 88 deletions src/qt/coincontroldialog.cpp
Expand Up @@ -143,15 +143,15 @@ CoinControlDialog::CoinControlDialog(QWidget* parent) :
// see https://github.com/bitcoin/bitcoin/issues/5716
ui->treeWidget->headerItem()->setText(COLUMN_CHECKBOX, QString());

ui->treeWidget->setColumnWidth(COLUMN_CHECKBOX, 84);
ui->treeWidget->setColumnWidth(COLUMN_CHECKBOX, 94);
ui->treeWidget->setColumnWidth(COLUMN_AMOUNT, 100);
ui->treeWidget->setColumnWidth(COLUMN_LABEL, 170);
ui->treeWidget->setColumnWidth(COLUMN_ADDRESS, 190);
ui->treeWidget->setColumnWidth(COLUMN_PRIVATESEND_ROUNDS, 88);
ui->treeWidget->setColumnWidth(COLUMN_DATE, 80);
ui->treeWidget->setColumnWidth(COLUMN_CONFIRMATIONS, 100);
ui->treeWidget->setColumnHidden(COLUMN_TXHASH, true); // store transaction hash in this column, but don't show it
ui->treeWidget->setColumnHidden(COLUMN_VOUT_INDEX, true); // store vout index in this column, but don't show it
ui->treeWidget->setColumnWidth(COLUMN_PRIVATESEND_ROUNDS, 110);
ui->treeWidget->setColumnWidth(COLUMN_DATE, 120);
ui->treeWidget->setColumnWidth(COLUMN_CONFIRMATIONS, 110);

ui->treeWidget->header()->setStretchLastSection(false);
ui->treeWidget->header()->setSectionResizeMode(COLUMN_ADDRESS, QHeaderView::Stretch);

// default view is sorted by amount desc
sortView(COLUMN_AMOUNT, Qt::DescendingOrder);
Expand Down Expand Up @@ -224,8 +224,12 @@ void CoinControlDialog::buttonToggleLockClicked()
ui->treeWidget->setEnabled(false);
for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++){
item = ui->treeWidget->topLevelItem(i);
COutPoint outpt(uint256S(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt());
if (model->isLockedCoin(uint256S(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt())){
COutPoint outpt(uint256S(item->data(COLUMN_ADDRESS, TxHashRole).toString().toStdString()), item->data(COLUMN_ADDRESS, VOutRole).toUInt());
// Don't toggle the lock state of partially mixed coins if they are not hidden in PrivateSend mode
if (coinControl()->IsUsingPrivateSend() && !fHideAdditional && !model->isFullyMixed(outpt)) {
continue;
}
if (model->isLockedCoin(uint256S(item->data(COLUMN_ADDRESS, TxHashRole).toString().toStdString()), item->data(COLUMN_ADDRESS, VOutRole).toUInt())){
model->unlockCoin(outpt);
item->setDisabled(false);
item->setIcon(COLUMN_CHECKBOX, QIcon());
Expand Down Expand Up @@ -257,10 +261,10 @@ void CoinControlDialog::showMenu(const QPoint &point)
contextMenuItem = item;

// disable some items (like Copy Transaction ID, lock, unlock) for tree roots in context menu
if (item->text(COLUMN_TXHASH).length() == 64) // transaction hash is 64 characters (this means its a child node, so its not a parent node in tree mode)
if (item->data(COLUMN_ADDRESS, TxHashRole).toString().length() == 64) // transaction hash is 64 characters (this means its a child node, so its not a parent node in tree mode)
{
copyTransactionHashAction->setEnabled(true);
if (model->isLockedCoin(uint256S(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt()))
if (model->isLockedCoin(uint256S(item->data(COLUMN_ADDRESS, TxHashRole).toString().toStdString()), item->data(COLUMN_ADDRESS, VOutRole).toUInt()))
{
lockAction->setEnabled(false);
unlockAction->setEnabled(true);
Expand Down Expand Up @@ -310,7 +314,7 @@ void CoinControlDialog::copyAddress()
// context menu action: copy transaction id
void CoinControlDialog::copyTransactionHash()
{
GUIUtil::setClipboard(contextMenuItem->text(COLUMN_TXHASH));
GUIUtil::setClipboard(contextMenuItem->data(COLUMN_ADDRESS, TxHashRole).toString());
}

// context menu action: lock coin
Expand All @@ -319,7 +323,7 @@ void CoinControlDialog::lockCoin()
if (contextMenuItem->checkState(COLUMN_CHECKBOX) == Qt::Checked)
contextMenuItem->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked);

COutPoint outpt(uint256S(contextMenuItem->text(COLUMN_TXHASH).toStdString()), contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt());
COutPoint outpt(uint256S(contextMenuItem->data(COLUMN_ADDRESS, TxHashRole).toString().toStdString()), contextMenuItem->data(COLUMN_ADDRESS, VOutRole).toUInt());
model->lockCoin(outpt);
contextMenuItem->setDisabled(true);
contextMenuItem->setIcon(COLUMN_CHECKBOX, GUIUtil::getIcon("lock_closed", GUIUtil::ThemedColor::RED));
Expand All @@ -329,7 +333,7 @@ void CoinControlDialog::lockCoin()
// context menu action: unlock coin
void CoinControlDialog::unlockCoin()
{
COutPoint outpt(uint256S(contextMenuItem->text(COLUMN_TXHASH).toStdString()), contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt());
COutPoint outpt(uint256S(contextMenuItem->data(COLUMN_ADDRESS, TxHashRole).toString().toStdString()), contextMenuItem->data(COLUMN_ADDRESS, VOutRole).toUInt());
model->unlockCoin(outpt);
contextMenuItem->setDisabled(false);
contextMenuItem->setIcon(COLUMN_CHECKBOX, QIcon());
Expand Down Expand Up @@ -425,9 +429,9 @@ void CoinControlDialog::radioListMode(bool checked)
// checkbox clicked by user
void CoinControlDialog::viewItemChanged(QTreeWidgetItem* item, int column)
{
if (column == COLUMN_CHECKBOX && item->text(COLUMN_TXHASH).length() == 64) // transaction hash is 64 characters (this means its a child node, so its not a parent node in tree mode)
if (column == COLUMN_CHECKBOX && item->data(COLUMN_ADDRESS, TxHashRole).toString().length() == 64) // transaction hash is 64 characters (this means its a child node, so its not a parent node in tree mode)
{
COutPoint outpt(uint256S(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt());
COutPoint outpt(uint256S(item->data(COLUMN_ADDRESS, TxHashRole).toString().toStdString()), item->data(COLUMN_ADDRESS, VOutRole).toUInt());

if (item->checkState(COLUMN_CHECKBOX) == Qt::Unchecked)
coinControl()->UnSelect(outpt);
Expand Down Expand Up @@ -466,6 +470,13 @@ void CoinControlDialog::updateLabelLocked()
else ui->labelLocked->setVisible(false);
}

void CoinControlDialog::on_hideButton_clicked()
{
fHideAdditional = !fHideAdditional;
updateView();
CoinControlDialog::updateLabels(model, this);
}

void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
{
if (!model)
Expand Down Expand Up @@ -683,6 +694,37 @@ void CoinControlDialog::updateView()
if (!model || !model->getOptionsModel() || !model->getAddressTableModel())
return;

bool fNormalMode = mode == Mode::NORMAL;
ui->treeWidget->setColumnHidden(COLUMN_PRIVATESEND_ROUNDS, fNormalMode);
ui->treeWidget->setColumnHidden(COLUMN_LABEL, !fNormalMode);
ui->radioTreeMode->setVisible(fNormalMode);
ui->radioListMode->setVisible(fNormalMode);

if (!CPrivateSendClientOptions::IsEnabled()) {
fHideAdditional = false;
ui->hideButton->setVisible(false);
}

QString strHideButton;
switch (mode) {
case Mode::NORMAL:
if (fHideAdditional) {
strHideButton = tr("Show all coins");
} else {
strHideButton = tr("Hide PrivateSend coins");
}
break;
case Mode::PRIVATESEND:
if (fHideAdditional) {
strHideButton = tr("Show all PrivateSend coins");
} else {
strHideButton = tr("Show spendable coins only");
}
ui->radioListMode->setChecked(true);
break;
}
ui->hideButton->setText(strHideButton);

bool treeMode = ui->radioTreeMode->isChecked();
ui->treeWidget->clear();
ui->treeWidget->setEnabled(false); // performance, otherwise updateLabels would be called for every checked checkbox
Expand Down Expand Up @@ -724,89 +766,108 @@ void CoinControlDialog::updateView()
int nChildren = 0;
for (const COutput& out : coins.second) {
COutPoint outpoint = COutPoint(out.tx->tx->GetHash(), out.i);
bool fFullyMixed{false};
CAmount nAmount = out.tx->tx->vout[out.i].nValue;

if ((coinControl()->IsUsingPrivateSend() && model->isFullyMixed(outpoint)) || !(coinControl()->IsUsingPrivateSend())) {
nSum += out.tx->tx->vout[out.i].nValue;
nChildren++;
bool fPrivateSendAmount = CPrivateSend::IsDenominatedAmount(nAmount) || CPrivateSend::IsCollateralAmount(nAmount);

CCoinControlWidgetItem* itemOutput;
if (treeMode) {
itemOutput = new CCoinControlWidgetItem(itemWalletAddress);
} else {
itemOutput = new CCoinControlWidgetItem(ui->treeWidget);
if (coinControl()->IsUsingPrivateSend()) {
fFullyMixed = model->isFullyMixed(outpoint);
if ((fHideAdditional && !fFullyMixed) || (!fHideAdditional && !fPrivateSendAmount)) {
coinControl()->UnSelect(outpoint);
continue;
}
} else {
if (fHideAdditional && fPrivateSendAmount) {
coinControl()->UnSelect(outpoint);
continue;
}
itemOutput->setFlags(flgCheckbox);
itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked);
}

// address
CTxDestination outputAddress;
QString sAddress = "";
if (ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, outputAddress)) {
sAddress = QString::fromStdString(EncodeDestination(outputAddress));
nSum += out.tx->tx->vout[out.i].nValue;
nChildren++;

// if listMode or change => show dash address. In tree mode, address is not shown again for direct wallet address outputs
if (!treeMode || (!(sAddress == sWalletAddress))) {
itemOutput->setText(COLUMN_ADDRESS, sAddress);
}
CCoinControlWidgetItem* itemOutput;
if (treeMode) {
itemOutput = new CCoinControlWidgetItem(itemWalletAddress);
} else {
itemOutput = new CCoinControlWidgetItem(ui->treeWidget);
}
itemOutput->setFlags(flgCheckbox);
itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked);

itemOutput->setToolTip(COLUMN_ADDRESS, sAddress);
// address
CTxDestination outputAddress;
QString sAddress = "";
if (ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, outputAddress)) {
sAddress = QString::fromStdString(EncodeDestination(outputAddress));

// if listMode or change => show dash address. In tree mode, address is not shown again for direct wallet address outputs
if (!treeMode || (!(sAddress == sWalletAddress))) {
itemOutput->setText(COLUMN_ADDRESS, sAddress);
}

// label
if (!(sAddress == sWalletAddress)) { //change
// tooltip from where the change comes from
itemOutput->setToolTip(COLUMN_LABEL, tr("change from %1 (%2)").arg(sWalletLabel).arg(sWalletAddress));
itemOutput->setText(COLUMN_LABEL, tr("(change)"));
} else if (!treeMode) {
QString sLabel = model->getAddressTableModel()->labelForAddress(sAddress);
if (sLabel.isEmpty()) {
sLabel = tr("(no label)");
}
itemOutput->setText(COLUMN_LABEL, sLabel);
}
itemOutput->setToolTip(COLUMN_ADDRESS, sAddress);
}

// amount
itemOutput->setText(COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, out.tx->tx->vout[out.i].nValue));
itemOutput->setToolTip(COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, out.tx->tx->vout[out.i].nValue));
itemOutput->setData(COLUMN_AMOUNT, Qt::UserRole, QVariant((qlonglong)out.tx->tx->vout[out.i].nValue)); // padding so that sorting works correctly

// date
itemOutput->setText(COLUMN_DATE, GUIUtil::dateTimeStr(out.tx->GetTxTime()));
itemOutput->setToolTip(COLUMN_DATE, GUIUtil::dateTimeStr(out.tx->GetTxTime()));
itemOutput->setData(COLUMN_DATE, Qt::UserRole, QVariant((qlonglong)out.tx->GetTxTime()));

// PrivateSend rounds
int nRounds = model->getRealOutpointPrivateSendRounds(outpoint);
if (nRounds >= 0 || LogAcceptCategory(BCLog::PRIVATESEND)) {
itemOutput->setText(COLUMN_PRIVATESEND_ROUNDS, QString::number(nRounds));
} else {
itemOutput->setText(COLUMN_PRIVATESEND_ROUNDS, tr("n/a"));
// label
if (!(sAddress == sWalletAddress)) { //change
// tooltip from where the change comes from
itemOutput->setToolTip(COLUMN_LABEL, tr("change from %1 (%2)").arg(sWalletLabel).arg(sWalletAddress));
itemOutput->setText(COLUMN_LABEL, tr("(change)"));
} else if (!treeMode) {
QString sLabel = model->getAddressTableModel()->labelForAddress(sAddress);
if (sLabel.isEmpty()) {
sLabel = tr("(no label)");
}
itemOutput->setData(COLUMN_PRIVATESEND_ROUNDS, Qt::UserRole, QVariant((qlonglong)nRounds));
itemOutput->setText(COLUMN_LABEL, sLabel);
}

// amount
itemOutput->setText(COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, out.tx->tx->vout[out.i].nValue));
itemOutput->setToolTip(COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, out.tx->tx->vout[out.i].nValue));
itemOutput->setData(COLUMN_AMOUNT, Qt::UserRole, QVariant((qlonglong)out.tx->tx->vout[out.i].nValue)); // padding so that sorting works correctly

// date
itemOutput->setText(COLUMN_DATE, GUIUtil::dateTimeStr(out.tx->GetTxTime()));
itemOutput->setToolTip(COLUMN_DATE, GUIUtil::dateTimeStr(out.tx->GetTxTime()));
itemOutput->setData(COLUMN_DATE, Qt::UserRole, QVariant((qlonglong)out.tx->GetTxTime()));

// PrivateSend rounds
int nRounds = model->getRealOutpointPrivateSendRounds(outpoint);
if (nRounds >= 0 || LogAcceptCategory(BCLog::PRIVATESEND)) {
itemOutput->setText(COLUMN_PRIVATESEND_ROUNDS, QString::number(nRounds));
} else {
itemOutput->setText(COLUMN_PRIVATESEND_ROUNDS, tr("n/a"));
}
itemOutput->setData(COLUMN_PRIVATESEND_ROUNDS, Qt::UserRole, QVariant((qlonglong)nRounds));

// confirmations
itemOutput->setText(COLUMN_CONFIRMATIONS, QString::number(out.nDepth));
itemOutput->setData(COLUMN_CONFIRMATIONS, Qt::UserRole, QVariant((qlonglong)out.nDepth));
// confirmations
itemOutput->setText(COLUMN_CONFIRMATIONS, QString::number(out.nDepth));
itemOutput->setData(COLUMN_CONFIRMATIONS, Qt::UserRole, QVariant((qlonglong)out.nDepth));

// transaction hash
uint256 txhash = out.tx->GetHash();
itemOutput->setText(COLUMN_TXHASH, QString::fromStdString(txhash.GetHex()));
// transaction hash
uint256 txhash = out.tx->GetHash();
itemOutput->setData(COLUMN_ADDRESS, TxHashRole, QString::fromStdString(txhash.GetHex()));

// vout index
itemOutput->setText(COLUMN_VOUT_INDEX, QString::number(out.i));
// vout index
itemOutput->setData(COLUMN_ADDRESS, VOutRole, out.i);

// disable locked coins
if (model->isLockedCoin(txhash, out.i)) {
COutPoint outpt(txhash, out.i);
coinControl()->UnSelect(outpt); // just to be sure
itemOutput->setDisabled(true);
itemOutput->setIcon(COLUMN_CHECKBOX, GUIUtil::getIcon("lock_closed", GUIUtil::ThemedColor::RED));
}
// disable locked coins
if (model->isLockedCoin(txhash, out.i)) {
COutPoint outpt(txhash, out.i);
coinControl()->UnSelect(outpt); // just to be sure
itemOutput->setDisabled(true);
itemOutput->setIcon(COLUMN_CHECKBOX, GUIUtil::getIcon("lock_closed", GUIUtil::ThemedColor::RED));
}

// set checkbox
if (coinControl()->IsSelected(COutPoint(txhash, out.i))) {
itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Checked);
}
// set checkbox
if (coinControl()->IsSelected(COutPoint(txhash, out.i))) {
itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Checked);
}

if (coinControl()->IsUsingPrivateSend() && !fHideAdditional && !fFullyMixed) {
itemOutput->setDisabled(true);
}
}

Expand All @@ -820,12 +881,15 @@ void CoinControlDialog::updateView()
}
}

// expand all partially selected
// expand all partially selected and hide the empty
if (treeMode)
{
for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++)
if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked)
ui->treeWidget->topLevelItem(i)->setExpanded(true);
for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) {
QTreeWidgetItem* topLevelItem = ui->treeWidget->topLevelItem(i);
topLevelItem->setHidden(topLevelItem->childCount() == 0);
if (topLevelItem->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked)
topLevelItem->setExpanded(true);
}
}

// sort view
Expand Down
12 changes: 10 additions & 2 deletions src/qt/coincontroldialog.h
Expand Up @@ -67,6 +67,8 @@ class CoinControlDialog : public QDialog
QAction *lockAction;
QAction *unlockAction;

bool fHideAdditional{true};

void sortView(int, Qt::SortOrder);
void updateView();

Expand All @@ -79,9 +81,14 @@ class CoinControlDialog : public QDialog
COLUMN_PRIVATESEND_ROUNDS,
COLUMN_DATE,
COLUMN_CONFIRMATIONS,
COLUMN_TXHASH,
COLUMN_VOUT_INDEX,
};

enum
{
TxHashRole = Qt::UserRole,
VOutRole
};

friend class CCoinControlWidgetItem;

enum class Mode {
Expand Down Expand Up @@ -114,6 +121,7 @@ private Q_SLOTS:
void buttonSelectAllClicked();
void buttonToggleLockClicked();
void updateLabelLocked();
void on_hideButton_clicked();
};

#endif // BITCOIN_QT_COINCONTROLDIALOG_H

0 comments on commit f4e3bf9

Please sign in to comment.