Skip to content

Commit 025cc48

Browse files
feat(qt): add shift-click range selection to coin control dialog
Override mouse events in CoinControlTreeWidget to support shift-click for selecting/deselecting a contiguous range of UTXOs. Tracks an anchor item from the last normal click, and on shift-click sets all leaf items between anchor and target to the anchor's check state. Uses the existing setEnabled(false) bulk-update pattern to suppress per-item label recalculation.
1 parent ac94680 commit 025cc48

4 files changed

Lines changed: 113 additions & 1 deletion

File tree

src/qt/coincontroldialog.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,11 @@ CoinControlDialog::~CoinControlDialog()
166166
delete ui;
167167
}
168168

169+
void CoinControlDialog::refreshLabels()
170+
{
171+
CoinControlDialog::updateLabels(m_coin_control, model, this);
172+
}
173+
169174
// ok button
170175
void CoinControlDialog::buttonBoxClicked(QAbstractButton* button)
171176
{
@@ -650,6 +655,7 @@ void CoinControlDialog::updateView()
650655

651656
bool treeMode = ui->radioTreeMode->isChecked();
652657
ui->treeWidget->clear();
658+
ui->treeWidget->resetAnchor();
653659
ui->treeWidget->setEnabled(false); // performance, otherwise updateLabels would be called for every checked checkbox
654660
ui->treeWidget->setAlternatingRowColors(!treeMode);
655661
QFlags<Qt::ItemFlag> flgCheckbox = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;

src/qt/coincontroldialog.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ class CoinControlDialog : public QDialog
4848
// static because also called from sendcoinsdialog
4949
static void updateLabels(wallet::CCoinControl& m_coin_control, WalletModel*, QDialog*);
5050

51+
void refreshLabels();
52+
5153
static QList<CAmount> payAmounts;
5254
static bool fSubtractFeeFromAmount;
5355

src/qt/coincontroltreewidget.cpp

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,20 @@
55
#include <qt/coincontroltreewidget.h>
66
#include <qt/coincontroldialog.h>
77

8+
#include <QTreeWidgetItemIterator>
9+
810
CoinControlTreeWidget::CoinControlTreeWidget(QWidget *parent) :
911
QTreeWidget(parent)
1012
{
1113

1214
}
1315

16+
void CoinControlTreeWidget::resetAnchor()
17+
{
18+
m_lastClickedItem = nullptr;
19+
m_lastClickedState = Qt::Unchecked;
20+
}
21+
1422
void CoinControlTreeWidget::keyPressEvent(QKeyEvent *event)
1523
{
1624
if (event->key() == Qt::Key_Space) // press spacebar -> select checkbox
@@ -32,3 +40,91 @@ void CoinControlTreeWidget::keyPressEvent(QKeyEvent *event)
3240
this->QTreeWidget::keyPressEvent(event);
3341
}
3442
}
43+
44+
// Helper: check if an item is a leaf node (UTXO) by its 64-char tx hash
45+
static bool isLeafItem(QTreeWidgetItem* item)
46+
{
47+
int COLUMN_ADDRESS = 3;
48+
return item && item->data(COLUMN_ADDRESS, Qt::UserRole).toString().length() == 64;
49+
}
50+
51+
void CoinControlTreeWidget::mousePressEvent(QMouseEvent *event)
52+
{
53+
QTreeWidgetItem* clickedItem = itemAt(event->pos());
54+
55+
// For shift+click on a leaf item with a valid anchor, suppress the default
56+
// press handling to prevent Qt from setting up a single-item checkbox toggle
57+
if ((event->button() == Qt::LeftButton)
58+
&& (event->modifiers() & Qt::ShiftModifier)
59+
&& m_lastClickedItem
60+
&& clickedItem
61+
&& clickedItem != m_lastClickedItem
62+
&& isLeafItem(clickedItem)) {
63+
event->accept();
64+
return;
65+
}
66+
67+
QTreeWidget::mousePressEvent(event);
68+
}
69+
70+
void CoinControlTreeWidget::mouseReleaseEvent(QMouseEvent *event)
71+
{
72+
int COLUMN_CHECKBOX = 0;
73+
74+
QTreeWidgetItem* clickedItem = itemAt(event->pos());
75+
76+
bool isShiftClick = (event->button() == Qt::LeftButton)
77+
&& (event->modifiers() & Qt::ShiftModifier)
78+
&& m_lastClickedItem
79+
&& clickedItem
80+
&& clickedItem != m_lastClickedItem
81+
&& isLeafItem(clickedItem);
82+
83+
if (!isShiftClick) {
84+
// Normal click — let Qt handle the checkbox toggle on release
85+
QTreeWidget::mouseReleaseEvent(event);
86+
// Record anchor after toggle so we capture the post-toggle state
87+
if (clickedItem && isLeafItem(clickedItem)) {
88+
m_lastClickedItem = clickedItem;
89+
m_lastClickedState = clickedItem->checkState(COLUMN_CHECKBOX);
90+
}
91+
return;
92+
}
93+
94+
// Shift+click: select/deselect the range between anchor and target
95+
96+
// Collect all leaf items in display order
97+
std::vector<QTreeWidgetItem*> leafItems;
98+
int anchorIdx = -1;
99+
int targetIdx = -1;
100+
QTreeWidgetItemIterator it(this);
101+
while (*it) {
102+
if (isLeafItem(*it)) {
103+
if (*it == m_lastClickedItem) anchorIdx = leafItems.size();
104+
if (*it == clickedItem) targetIdx = leafItems.size();
105+
leafItems.push_back(*it);
106+
}
107+
++it;
108+
}
109+
110+
if (anchorIdx < 0 || targetIdx < 0) {
111+
QTreeWidget::mouseReleaseEvent(event);
112+
return;
113+
}
114+
115+
if (anchorIdx > targetIdx) std::swap(anchorIdx, targetIdx);
116+
117+
// Batch update: disable widget to suppress per-item updateLabels calls
118+
setEnabled(false);
119+
for (int i = anchorIdx; i <= targetIdx; ++i) {
120+
QTreeWidgetItem* item = leafItems[i];
121+
if (!item->isDisabled() && item->checkState(COLUMN_CHECKBOX) != m_lastClickedState) {
122+
item->setCheckState(COLUMN_CHECKBOX, m_lastClickedState);
123+
}
124+
}
125+
setEnabled(true);
126+
127+
// Single label update for the whole batch
128+
CoinControlDialog* coinControlDialog = qobject_cast<CoinControlDialog*>(this->parentWidget());
129+
if (coinControlDialog) coinControlDialog->refreshLabels();
130+
}

src/qt/coincontroltreewidget.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#define BITCOIN_QT_COINCONTROLTREEWIDGET_H
77

88
#include <QKeyEvent>
9+
#include <QMouseEvent>
910
#include <QTreeWidget>
1011

1112
class CoinControlTreeWidget : public QTreeWidget
@@ -14,9 +15,16 @@ class CoinControlTreeWidget : public QTreeWidget
1415

1516
public:
1617
explicit CoinControlTreeWidget(QWidget *parent = nullptr);
18+
void resetAnchor();
1719

1820
protected:
19-
virtual void keyPressEvent(QKeyEvent *event) override;
21+
void keyPressEvent(QKeyEvent *event) override;
22+
void mousePressEvent(QMouseEvent *event) override;
23+
void mouseReleaseEvent(QMouseEvent *event) override;
24+
25+
private:
26+
QTreeWidgetItem* m_lastClickedItem{nullptr};
27+
Qt::CheckState m_lastClickedState{Qt::Unchecked};
2028
};
2129

2230
#endif // BITCOIN_QT_COINCONTROLTREEWIDGET_H

0 commit comments

Comments
 (0)