mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-22 17:45:09 -07:00
[Models] Route group lookups around mirrored custom zones
Group lookups (createNodeIfNeeded, findCardNode) must not resolve a mirrored custom zone that shares the group name. Introduce findGroupChild to search only non-custom children, and make addCard consult the deck tree before falling back to creating a top-level zone so cards added to an un-mirrored custom zone land inside it. mirrorCustomZones now flattens cards nested at any depth into the mirrored zone so no card is left without a model row. Add model behaviour tests (addCard routing, same-name group/zone collision, removeRows guard, empty-zone survival, findCard inside a custom zone) and fix the missing main() in the unit test binaries.
This commit is contained in:
parent
80784d4e02
commit
e221d8e1bb
6 changed files with 303 additions and 12 deletions
|
|
@ -378,7 +378,8 @@ bool DeckListModel::removeRows(int row, int count, const QModelIndex &parent)
|
|||
|
||||
InnerDecklistNode *DeckListModel::createNodeIfNeeded(const QString &name, InnerDecklistNode *parent)
|
||||
{
|
||||
auto *newNode = dynamic_cast<InnerDecklistNode *>(parent->findChild(name));
|
||||
// Group lookups must not resolve a mirrored custom zone that shares the name.
|
||||
auto *newNode = DeckListModelCustomZones::findGroupChild(parent, name);
|
||||
if (!newNode) {
|
||||
beginInsertRows(nodeToIndex(parent), parent->size(), parent->size());
|
||||
newNode = new InnerDecklistNode(name, parent);
|
||||
|
|
@ -401,7 +402,7 @@ DecklistModelCardNode *DeckListModel::findCardNode(const QString &cardName,
|
|||
// nested under the board.
|
||||
if (auto *zoneNode = dynamic_cast<InnerDecklistNode *>(root->findChild(zoneName))) {
|
||||
QString groupCriteria = extractGroupCriteriaValue(info, activeGroupCriteria);
|
||||
if (auto *groupNode = dynamic_cast<InnerDecklistNode *>(zoneNode->findChild(groupCriteria))) {
|
||||
if (auto *groupNode = DeckListModelCustomZones::findGroupChild(zoneNode, groupCriteria)) {
|
||||
if (auto *card = dynamic_cast<DecklistModelCardNode *>(
|
||||
groupNode->findCardChildByNameProviderIdAndNumber(cardName, providerId, cardNumber))) {
|
||||
return card;
|
||||
|
|
@ -486,6 +487,26 @@ QModelIndex DeckListModel::addCard(const ExactCard &card, const QString &zoneNam
|
|||
// Custom zone: cards live flat inside the zone.
|
||||
cardParent = customZoneNode;
|
||||
} else {
|
||||
// Not present in the shadow tree. The deck tree may still hold a custom
|
||||
// zone that has not been mirrored (callers can add a zone and then a
|
||||
// card without a rebuild). Check before falling back to creating a
|
||||
// top-level zone the deck does not actually have.
|
||||
auto *listRoot = deckList->getTree()->getRoot();
|
||||
bool hasDeckZone = false;
|
||||
for (int i = 0; i < listRoot->size(); ++i) {
|
||||
if (auto *boardZone = dynamic_cast<InnerDecklistNode *>(listRoot->at(i))) {
|
||||
if (boardZone->findChild(zoneName)) {
|
||||
hasDeckZone = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasDeckZone) {
|
||||
rebuildTree();
|
||||
return addCard(card, zoneName);
|
||||
}
|
||||
|
||||
// Unknown zone: create a top-level zone (legacy behavior).
|
||||
QString groupCriteria = extractGroupCriteriaValue(cardInfo, activeGroupCriteria);
|
||||
auto *newZone = createNodeIfNeeded(zoneName, root);
|
||||
|
|
|
|||
|
|
@ -14,26 +14,55 @@ bool isCustomZone(const AbstractDecklistNode *node)
|
|||
return dynamic_cast<const DecklistModelSubZoneNode *>(node) != nullptr;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Flattens every card under @p zone into @p shadowZone, preserving order.
|
||||
*
|
||||
* Custom zones mirror as a single row level: cards nested in sub-zones of any
|
||||
* depth are added as direct children of the mirrored zone so no card is left
|
||||
* without a model row.
|
||||
*/
|
||||
void flattenCards(const InnerDecklistNode *zone, InnerDecklistNode *shadowZone)
|
||||
{
|
||||
for (int k = 0; k < zone->size(); k++) {
|
||||
if (auto *zoneCard = dynamic_cast<DecklistCardNode *>(zone->at(k))) {
|
||||
new DecklistModelCardNode(zoneCard, shadowZone);
|
||||
} else if (auto *subZone = dynamic_cast<const InnerDecklistNode *>(zone->at(k))) {
|
||||
flattenCards(subZone, shadowZone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void mirrorCustomZones(const InnerDecklistNode *deckBoardZone, InnerDecklistNode *shadowBoardZone)
|
||||
{
|
||||
for (int j = 0; j < deckBoardZone->size(); j++) {
|
||||
auto *customCard = dynamic_cast<DecklistCardNode *>(deckBoardZone->at(j));
|
||||
if (customCard) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto *customZone = dynamic_cast<const InnerDecklistNode *>(deckBoardZone->at(j));
|
||||
if (!customZone) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto *shadowZone = new DecklistModelSubZoneNode(customZone->getName(), shadowBoardZone);
|
||||
for (int k = 0; k < customZone->size(); k++) {
|
||||
if (auto *zoneCard = dynamic_cast<DecklistCardNode *>(customZone->at(k))) {
|
||||
new DecklistModelCardNode(zoneCard, shadowZone);
|
||||
}
|
||||
flattenCards(customZone, shadowZone);
|
||||
}
|
||||
}
|
||||
|
||||
InnerDecklistNode *findGroupChild(InnerDecklistNode *parent, const QString &name)
|
||||
{
|
||||
for (int i = 0; i < parent->size(); i++) {
|
||||
AbstractDecklistNode *child = parent->at(i);
|
||||
if (isCustomZone(child)) {
|
||||
continue;
|
||||
}
|
||||
auto *group = dynamic_cast<InnerDecklistNode *>(child);
|
||||
if (group && group->getName() == name) {
|
||||
return group;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DecklistModelSubZoneNode *findSubZoneByName(InnerDecklistNode *root, const QString &zoneName)
|
||||
|
|
|
|||
|
|
@ -43,6 +43,21 @@ namespace DeckListModelCustomZones
|
|||
*/
|
||||
[[nodiscard]] bool isCustomZone(const AbstractDecklistNode *node);
|
||||
|
||||
/**
|
||||
* @brief Finds a criteria-group child of @p parent by name, skipping custom zones.
|
||||
*
|
||||
* The shadow tree keeps criteria groups and mirrored custom zones as siblings
|
||||
* under a board zone, and `InnerDecklistNode::findChild` matches both by name.
|
||||
* Group lookups must not resolve a custom zone that happens to share the group
|
||||
* name (e.g. a zone called "Creature"), so this searches only non-custom
|
||||
* children.
|
||||
*
|
||||
* @param parent The shadow node whose children are searched.
|
||||
* @param name The group name to find.
|
||||
* @return The matching group node, or nullptr if none exists.
|
||||
*/
|
||||
[[nodiscard]] InnerDecklistNode *findGroupChild(InnerDecklistNode *parent, const QString &name);
|
||||
|
||||
/**
|
||||
* @brief Mirrors the custom zones of a deck board zone into its shadow board node.
|
||||
*
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue