55#include < qt/coincontroltreewidget.h>
66#include < qt/coincontroldialog.h>
77
8+ #include < QTreeWidgetItemIterator>
9+
810CoinControlTreeWidget::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+
1422void 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+ }
0 commit comments