Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/qt/coincontroldialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@ CoinControlDialog::~CoinControlDialog()
delete ui;
}

void CoinControlDialog::refreshLabels()
{
CoinControlDialog::updateLabels(m_coin_control, model, this);
}

// ok button
void CoinControlDialog::buttonBoxClicked(QAbstractButton* button)
{
Expand Down Expand Up @@ -650,6 +655,7 @@ void CoinControlDialog::updateView()

bool treeMode = ui->radioTreeMode->isChecked();
ui->treeWidget->clear();
ui->treeWidget->resetAnchor();
ui->treeWidget->setEnabled(false); // performance, otherwise updateLabels would be called for every checked checkbox
ui->treeWidget->setAlternatingRowColors(!treeMode);
QFlags<Qt::ItemFlag> flgCheckbox = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
Expand Down
2 changes: 2 additions & 0 deletions src/qt/coincontroldialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ class CoinControlDialog : public QDialog
// static because also called from sendcoinsdialog
static void updateLabels(wallet::CCoinControl& m_coin_control, WalletModel*, QDialog*);

void refreshLabels();

static QList<CAmount> payAmounts;
static bool fSubtractFeeFromAmount;

Expand Down
115 changes: 115 additions & 0 deletions src/qt/coincontroltreewidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,21 @@
#include <qt/coincontroltreewidget.h>
#include <qt/coincontroldialog.h>

#include <QStyle>
#include <QStyleOptionViewItem>
#include <QTreeWidgetItemIterator>

CoinControlTreeWidget::CoinControlTreeWidget(QWidget *parent) :
QTreeWidget(parent)
{

}

void CoinControlTreeWidget::resetAnchor()
{
m_lastClickedItem = nullptr;
}

void CoinControlTreeWidget::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Space) // press spacebar -> select checkbox
Expand All @@ -32,3 +41,109 @@ void CoinControlTreeWidget::keyPressEvent(QKeyEvent *event)
this->QTreeWidget::keyPressEvent(event);
}
}

// Helper: check if an item is a leaf node (UTXO) by its 64-char tx hash
static bool isLeafItem(QTreeWidgetItem* item)
{
int COLUMN_ADDRESS = 3;
return item && item->data(COLUMN_ADDRESS, Qt::UserRole).toString().length() == 64;
}

bool CoinControlTreeWidget::isCheckboxClick(QTreeWidgetItem* item, const QPoint& pos) const
{
int COLUMN_CHECKBOX = 0;

if (!item || columnAt(pos.x()) != COLUMN_CHECKBOX) {
return false;
}

const QModelIndex index = indexFromItem(item, COLUMN_CHECKBOX);
if (!index.isValid()) {
return false;
}

QStyleOptionViewItem option;
option.initFrom(this);
option.rect = visualRect(index);
option.features = QStyleOptionViewItem::HasCheckIndicator;
option.checkState = item->checkState(COLUMN_CHECKBOX);

return style()->subElementRect(QStyle::SE_ItemViewItemCheckIndicator, &option, this).contains(pos);
}

void CoinControlTreeWidget::mouseReleaseEvent(QMouseEvent *event)
{
int COLUMN_CHECKBOX = 0;

QTreeWidgetItem* clickedItem = itemAt(event->pos());
const bool isCheckboxInteraction = isCheckboxClick(clickedItem, event->pos());

bool isShiftClick = (event->button() == Qt::LeftButton)
&& (event->modifiers() & Qt::ShiftModifier)
&& m_lastClickedItem
&& clickedItem
&& clickedItem != m_lastClickedItem
&& isCheckboxInteraction
&& isLeafItem(clickedItem)
&& !clickedItem->isDisabled()
&& !m_lastClickedItem->isDisabled();

if (!isShiftClick) {
// Normal click — let Qt handle the checkbox toggle on release
const Qt::CheckState previousState = clickedItem ? clickedItem->checkState(COLUMN_CHECKBOX) : Qt::Unchecked;
QTreeWidget::mouseReleaseEvent(event);
// Record anchor after toggle so we capture the post-toggle state
if (event->button() == Qt::LeftButton && clickedItem
&& isCheckboxInteraction
&& isLeafItem(clickedItem)
&& !clickedItem->isDisabled()
&& clickedItem->checkState(COLUMN_CHECKBOX) != previousState) {
m_lastClickedItem = clickedItem;
}
return;
}

// Shift+click: select/deselect the range between anchor and target
// Read the anchor's current check state live (not cached) so it stays
// correct after bulk operations like Select All or parent tristate changes
Qt::CheckState stateToApply = m_lastClickedItem->checkState(COLUMN_CHECKBOX);

// Collect visible leaf items in display order, skipping children of
// collapsed parent nodes so we don't toggle coins the user can't see
std::vector<QTreeWidgetItem*> leafItems;
int anchorIdx = -1;
int targetIdx = -1;
QTreeWidgetItemIterator it(this);
while (*it) {
if (isLeafItem(*it)) {
QTreeWidgetItem* parent = (*it)->parent();
if (!parent || parent->isExpanded()) {
if (*it == m_lastClickedItem) anchorIdx = leafItems.size();
if (*it == clickedItem) targetIdx = leafItems.size();
leafItems.push_back(*it);
}
}
++it;
}

if (anchorIdx < 0 || targetIdx < 0) {
QTreeWidget::mouseReleaseEvent(event);
return;
}

if (anchorIdx > targetIdx) std::swap(anchorIdx, targetIdx);

// Batch update: disable widget to suppress per-item updateLabels calls
setEnabled(false);
for (int i = anchorIdx; i <= targetIdx; ++i) {
QTreeWidgetItem* item = leafItems[i];
if (!item->isDisabled() && item->checkState(COLUMN_CHECKBOX) != stateToApply) {
item->setCheckState(COLUMN_CHECKBOX, stateToApply);
}
}
setEnabled(true);

// Single label update for the whole batch
CoinControlDialog* coinControlDialog = qobject_cast<CoinControlDialog*>(this->parentWidget());
if (coinControlDialog) coinControlDialog->refreshLabels();
}
10 changes: 9 additions & 1 deletion src/qt/coincontroltreewidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#define BITCOIN_QT_COINCONTROLTREEWIDGET_H

#include <QKeyEvent>
#include <QMouseEvent>
#include <QTreeWidget>

class CoinControlTreeWidget : public QTreeWidget
Expand All @@ -14,9 +15,16 @@ class CoinControlTreeWidget : public QTreeWidget

public:
explicit CoinControlTreeWidget(QWidget *parent = nullptr);
void resetAnchor();

protected:
virtual void keyPressEvent(QKeyEvent *event) override;
void keyPressEvent(QKeyEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;

private:
bool isCheckboxClick(QTreeWidgetItem* item, const QPoint& pos) const;

QTreeWidgetItem* m_lastClickedItem{nullptr};
};

#endif // BITCOIN_QT_COINCONTROLTREEWIDGET_H
Loading