From b13c682a7ac95fdec7b08465b8d514df5cbe310a Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Mon, 24 Aug 2026 18:44:05 +0200 Subject: [PATCH] [DeckList] Make deck tree card traversal recursive (#7175) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [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 --- .../widgets/server/user/user_list_widget.cpp | 30 +++++++-------- .../deck_list/deck_list_node_tree.cpp | 37 +++++++++++++------ .../deck_list/deck_list_node_tree.h | 5 ++- 3 files changed, 44 insertions(+), 28 deletions(-) diff --git a/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp b/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp index 779ee1b9c..c4b5d6af6 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp @@ -718,20 +718,20 @@ UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor, } }); - // Section dividers can be collapsed/expanded by the user. Surface those - // changes only from real user interaction. Programmatic expansion is - // applied through setSectionExpanded() / setExpandedProgrammatically(). - connect(userTree, &QTreeWidget::itemExpanded, this, - [this](QTreeWidgetItem *item) { handleSectionExpansion(item, true); }); - connect(userTree, &QTreeWidget::itemCollapsed, this, - [this](QTreeWidgetItem *item) { handleSectionExpansion(item, false); }); + // Section dividers can be collapsed/expanded by the user. Surface those + // changes only from real user interaction. Programmatic expansion is + // applied through setSectionExpanded() / setExpandedProgrammatically(). + connect(userTree, &QTreeWidget::itemExpanded, this, + [this](QTreeWidgetItem *item) { handleSectionExpansion(item, true); }); + connect(userTree, &QTreeWidget::itemCollapsed, this, + [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] { - showPopupTimer->stop(); - hidePopup(true); - requestAvatarsForVisibleItems(); - }); + showPopupTimer->stop(); + hidePopup(true); + requestVisibleItemResources(); + }); // Forward join requests from popup upward 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. connect(userTree->verticalScrollBar(), &QScrollBar::valueChanged, this, - [this] { requestAvatarsForVisibleItems(); }); + [this] { requestVisibleItemResources(); }); } // 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(); } diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.cpp index 196416cde..efe20595b 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.cpp @@ -41,13 +41,19 @@ QList DecklistNodeTree::getCardNodes(const QSet result; - for (auto *zoneNode : getZoneNodes(restrictToZones)) { - for (auto *cardNode : *zoneNode) { - auto *cardCardNode = dynamic_cast(cardNode); - if (cardCardNode) { - result.append(cardCardNode); + std::function collectCards = [&collectCards, + &result](const InnerDecklistNode *node) { + for (int i = 0; i < node->size(); i++) { + if (auto *card = dynamic_cast(node->at(i))) { + result.append(card); + } else if (auto *inner = dynamic_cast(node->at(i))) { + collectCards(inner); } } + }; + + for (auto *zoneNode : getZoneNodes(restrictToZones)) { + collectCards(zoneNode); } return result; @@ -160,13 +166,22 @@ bool DecklistNodeTree::deleteNode(AbstractDecklistNode *node, InnerDecklistNode void DecklistNodeTree::forEachCard(const std::function &func) const { - // Support for this is only possible if the internal structure - // doesn't get more complicated. + // Cards nested in custom zones are reported with their top-level board zone + // so that callers can classify cards by board (main/side/maybeboard/tokens). + std::function walk = [&func, &walk](InnerDecklistNode *boardZone, + InnerDecklistNode *node) { + for (int i = 0; i < node->size(); i++) { + if (auto *card = dynamic_cast(node->at(i))) { + func(boardZone, card); + } else if (auto *inner = dynamic_cast(node->at(i))) { + walk(boardZone, inner); + } + } + }; + for (int i = 0; i < root->size(); i++) { - InnerDecklistNode *node = dynamic_cast(root->at(i)); - for (int j = 0; j < node->size(); j++) { - DecklistCardNode *card = dynamic_cast(node->at(j)); - func(node, card); + if (auto *zone = dynamic_cast(root->at(i))) { + walk(zone, zone); } } } diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.h index 8ef0b18a5..eae20aa23 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.h @@ -78,9 +78,10 @@ public: 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 &func) const;