[DeckList] Make deck tree card traversal recursive (#7175)

* [DeckList] Make deck tree card traversal recursive

getCardNodes and forEachCard now descend into nested zones instead of
assuming a flat main/side/token layout. For today's flat trees this is
behavior-preserving; it also removes two latent crashes (unchecked
dynamic_cast dereference, null card nodes passed to forEachCard
callers). Nested zones are introduced by later custom-zones units.

Took 3 minutes


Took 7 seconds

Took 9 seconds

* Fix rebase mistake.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2026-08-24 18:44:05 +02:00 committed by GitHub
parent 22b0f69706
commit b13c682a7a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 44 additions and 28 deletions

View file

@ -718,20 +718,20 @@ UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor,
} }
}); });
// Section dividers can be collapsed/expanded by the user. Surface those // Section dividers can be collapsed/expanded by the user. Surface those
// changes only from real user interaction. Programmatic expansion is // changes only from real user interaction. Programmatic expansion is
// applied through setSectionExpanded() / setExpandedProgrammatically(). // applied through setSectionExpanded() / setExpandedProgrammatically().
connect(userTree, &QTreeWidget::itemExpanded, this, connect(userTree, &QTreeWidget::itemExpanded, this,
[this](QTreeWidgetItem *item) { handleSectionExpansion(item, true); }); [this](QTreeWidgetItem *item) { handleSectionExpansion(item, true); });
connect(userTree, &QTreeWidget::itemCollapsed, this, connect(userTree, &QTreeWidget::itemCollapsed, this,
[this](QTreeWidgetItem *item) { handleSectionExpansion(item, false); }); [this](QTreeWidgetItem *item) { handleSectionExpansion(item, false); });
// Hide popup when list scrolls (reference row has moved) // Hide popup when list scrolls (reference row has moved)
connect(userTree->verticalScrollBar(), &QScrollBar::valueChanged, this, [this] { connect(userTree->verticalScrollBar(), &QScrollBar::valueChanged, this, [this] {
showPopupTimer->stop(); showPopupTimer->stop();
hidePopup(true); hidePopup(true);
requestAvatarsForVisibleItems(); requestVisibleItemResources();
}); });
// Forward join requests from popup upward // Forward join requests from popup upward
connect(userInfoPopup, &UserInfoPopup::joinGameRequested, this, &UserListWidget::joinGameRequested); connect(userInfoPopup, &UserInfoPopup::joinGameRequested, this, &UserListWidget::joinGameRequested);
@ -746,7 +746,7 @@ UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor,
// Keep the popup-less scroll path alive for avatar prefetch. // Keep the popup-less scroll path alive for avatar prefetch.
connect(userTree->verticalScrollBar(), &QScrollBar::valueChanged, this, connect(userTree->verticalScrollBar(), &QScrollBar::valueChanged, this,
[this] { requestAvatarsForVisibleItems(); }); [this] { requestVisibleItemResources(); });
} }
// Section dividers can be collapsed/expanded by the user. Surface those // Section dividers can be collapsed/expanded by the user. Surface those
@ -1541,9 +1541,9 @@ void UserListWidget::updateCount()
} }
} }
void UserListWidget::setShowTitle(bool showTitle) void UserListWidget::setShowTitle(bool _showTitle)
{ {
this->showTitle = showTitle; this->showTitle = _showTitle;
updateCount(); updateCount();
} }

View file

@ -41,13 +41,19 @@ QList<const DecklistCardNode *> DecklistNodeTree::getCardNodes(const QSet<QStrin
{ {
QList<const DecklistCardNode *> result; QList<const DecklistCardNode *> result;
for (auto *zoneNode : getZoneNodes(restrictToZones)) { std::function<void(const InnerDecklistNode *)> collectCards = [&collectCards,
for (auto *cardNode : *zoneNode) { &result](const InnerDecklistNode *node) {
auto *cardCardNode = dynamic_cast<DecklistCardNode *>(cardNode); for (int i = 0; i < node->size(); i++) {
if (cardCardNode) { if (auto *card = dynamic_cast<const DecklistCardNode *>(node->at(i))) {
result.append(cardCardNode); result.append(card);
} else if (auto *inner = dynamic_cast<const InnerDecklistNode *>(node->at(i))) {
collectCards(inner);
} }
} }
};
for (auto *zoneNode : getZoneNodes(restrictToZones)) {
collectCards(zoneNode);
} }
return result; return result;
@ -160,13 +166,22 @@ bool DecklistNodeTree::deleteNode(AbstractDecklistNode *node, InnerDecklistNode
void DecklistNodeTree::forEachCard(const std::function<void(InnerDecklistNode *, DecklistCardNode *)> &func) const void DecklistNodeTree::forEachCard(const std::function<void(InnerDecklistNode *, DecklistCardNode *)> &func) const
{ {
// Support for this is only possible if the internal structure // Cards nested in custom zones are reported with their top-level board zone
// doesn't get more complicated. // so that callers can classify cards by board (main/side/maybeboard/tokens).
std::function<void(InnerDecklistNode *, InnerDecklistNode *)> walk = [&func, &walk](InnerDecklistNode *boardZone,
InnerDecklistNode *node) {
for (int i = 0; i < node->size(); i++) {
if (auto *card = dynamic_cast<DecklistCardNode *>(node->at(i))) {
func(boardZone, card);
} else if (auto *inner = dynamic_cast<InnerDecklistNode *>(node->at(i))) {
walk(boardZone, inner);
}
}
};
for (int i = 0; i < root->size(); i++) { for (int i = 0; i < root->size(); i++) {
InnerDecklistNode *node = dynamic_cast<InnerDecklistNode *>(root->at(i)); if (auto *zone = dynamic_cast<InnerDecklistNode *>(root->at(i))) {
for (int j = 0; j < node->size(); j++) { walk(zone, zone);
DecklistCardNode *card = dynamic_cast<DecklistCardNode *>(node->at(j));
func(node, card);
} }
} }
} }

View file

@ -78,9 +78,10 @@ public:
bool deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNode = nullptr); bool deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNode = nullptr);
/** /**
* @brief Apply a function to every card in the deck tree. This can modify the cards. * @brief Applies a function to every card in the deck tree. This can modify the cards.
* *
* @param func Function taking (zone node, card node). * @param func Function taking (top-level board zone node, card node). Cards nested
* in custom zones are reported with their board zone.
*/ */
void forEachCard(const std::function<void(InnerDecklistNode *, DecklistCardNode *)> &func) const; void forEachCard(const std::function<void(InnerDecklistNode *, DecklistCardNode *)> &func) const;