From 53e27ff4d3e26c6f379f102f245282e5e9cb2da4 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Sun, 22 Jun 2025 03:15:48 +0200 Subject: [PATCH] Printing Selector Bulk Editor (#5993) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Bulk editing dialog. * Bulk editing dialog functionality. * Performance fixes, hide sets which can't offer any new cards, better dragging indicators. * Update count label. * Add a display for modified cards. * Include long setName in checkbox label * Fix drag & drop. * New layout updating? * Re-layout, add instruction label. * Qt version check. * Add buttons to clear and set all to preferred printing. * tr UI * Add the button to the print selector instead. * Qt5 compatibility stuff. * Qt5 compatibility stuff again. * Toggled works, I guess. --------- Co-authored-by: Lukas BrĂ¼bach --- cockatrice/CMakeLists.txt | 1 + .../printing_selector/printing_selector.cpp | 1 + .../printing_selector/printing_selector.h | 5 + ...rinting_selector_card_selection_widget.cpp | 16 + .../printing_selector_card_selection_widget.h | 4 + cockatrice/src/deck/deck_loader.cpp | 57 ++ cockatrice/src/deck/deck_loader.h | 1 + .../src/dialogs/dlg_select_set_for_cards.cpp | 642 ++++++++++++++++++ .../src/dialogs/dlg_select_set_for_cards.h | 104 +++ cockatrice/src/game/cards/card_database.cpp | 9 +- common/decklist.cpp | 15 +- common/decklist.h | 2 +- 12 files changed, 848 insertions(+), 9 deletions(-) create mode 100644 cockatrice/src/dialogs/dlg_select_set_for_cards.cpp create mode 100644 cockatrice/src/dialogs/dlg_select_set_for_cards.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 035dc98fc..26cc72aca 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -170,6 +170,7 @@ set(cockatrice_SOURCES src/dialogs/dlg_move_top_cards_until.cpp src/dialogs/dlg_register.cpp src/dialogs/dlg_roll_dice.cpp + src/dialogs/dlg_select_set_for_cards.cpp src/dialogs/dlg_settings.cpp src/dialogs/dlg_tip_of_the_day.cpp src/dialogs/dlg_update.cpp diff --git a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector.cpp b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector.cpp index b75ca213e..0bb7061fd 100644 --- a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector.cpp +++ b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector.cpp @@ -1,5 +1,6 @@ #include "printing_selector.h" +#include "../../../../dialogs/dlg_select_set_for_cards.h" #include "../../../../settings/cache_settings.h" #include "../../picture_loader/picture_loader.h" #include "printing_selector_card_display_widget.h" diff --git a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector.h b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector.h index 515807831..388592208 100644 --- a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector.h +++ b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector.h @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -29,6 +30,10 @@ public: void setCard(const CardInfoPtr &newCard, const QString &_currentZone); void getAllSetsForCurrentCard(); + DeckListModel *getDeckModel() const + { + return deckModel; + }; public slots: void retranslateUi(); diff --git a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_selection_widget.cpp b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_selection_widget.cpp index b4cace6d0..238795273 100644 --- a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_selection_widget.cpp +++ b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_selection_widget.cpp @@ -1,5 +1,7 @@ #include "printing_selector_card_selection_widget.h" +#include "../../../../dialogs/dlg_select_set_for_cards.h" + /** * @brief Constructs a PrintingSelectorCardSelectionWidget for navigating through cards in the deck. * @@ -16,12 +18,18 @@ PrintingSelectorCardSelectionWidget::PrintingSelectorCardSelectionWidget(Printin previousCardButton = new QPushButton(this); previousCardButton->setText(tr("Previous Card in Deck")); + selectSetForCardsButton = new QPushButton(this); + connect(selectSetForCardsButton, &QPushButton::clicked, this, + &PrintingSelectorCardSelectionWidget::selectSetForCards); + selectSetForCardsButton->setText(tr("Bulk Selection")); + nextCardButton = new QPushButton(this); nextCardButton->setText(tr("Next Card in Deck")); connectSignals(); cardSelectionBarLayout->addWidget(previousCardButton); + cardSelectionBarLayout->addWidget(selectSetForCardsButton); cardSelectionBarLayout->addWidget(nextCardButton); } @@ -36,3 +44,11 @@ void PrintingSelectorCardSelectionWidget::connectSignals() connect(previousCardButton, &QPushButton::clicked, parent, &PrintingSelector::selectPreviousCard); connect(nextCardButton, &QPushButton::clicked, parent, &PrintingSelector::selectNextCard); } + +void PrintingSelectorCardSelectionWidget::selectSetForCards() +{ + DlgSelectSetForCards *setSelectionDialog = new DlgSelectSetForCards(nullptr, parent->getDeckModel()); + if (!setSelectionDialog->exec()) { + return; + } +} diff --git a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_selection_widget.h b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_selection_widget.h index 4bb2b048f..89229bda8 100644 --- a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_selection_widget.h +++ b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_selection_widget.h @@ -16,10 +16,14 @@ public: void connectSignals(); +public slots: + void selectSetForCards(); + private: PrintingSelector *parent; QHBoxLayout *cardSelectionBarLayout; QPushButton *previousCardButton; + QPushButton *selectSetForCardsButton; QPushButton *nextCardButton; }; diff --git a/cockatrice/src/deck/deck_loader.cpp b/cockatrice/src/deck/deck_loader.cpp index d0c0f5239..9d58db7e4 100644 --- a/cockatrice/src/deck/deck_loader.cpp +++ b/cockatrice/src/deck/deck_loader.cpp @@ -312,6 +312,44 @@ QString DeckLoader::exportDeckToDecklist(DecklistWebsite website) return deckString; } +// This struct is here to support the forEachCard function call, defined in decklist. +// It requires a function to be called for each card, and it will set the providerId to the preferred printing. +struct SetProviderIdToPreferred +{ + // Main operator for struct, allowing the foreachcard to work. + SetProviderIdToPreferred() + { + } + + void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const + { + Q_UNUSED(node); + CardInfoPerSet preferredSet = CardDatabaseManager::getInstance()->getSpecificSetForCard( + card->getName(), + CardDatabaseManager::getInstance()->getPreferredPrintingProviderIdForCard(card->getName())); + QString providerId = preferredSet.getProperty("uuid"); + QString setShortName = preferredSet.getPtr()->getShortName(); + QString collectorNumber = preferredSet.getProperty("num"); + + card->setCardProviderId(providerId); + card->setCardCollectorNumber(collectorNumber); + card->setCardSetShortName(setShortName); + } +}; + +/** + * This function iterates through each card in the decklist and sets the providerId + * on each card based on its set name and collector number. + */ +void DeckLoader::setProviderIdToPreferredPrinting() +{ + // Set up the struct to call. + SetProviderIdToPreferred setProviderIdToPreferred; + + // Call the forEachCard method for each card in the deck + forEachCard(setProviderIdToPreferred); +} + /** * Sets the providerId on each card in the decklist based on its set name and collector number. */ @@ -332,6 +370,25 @@ void DeckLoader::resolveSetNameAndNumberToProviderID() forEachCard(setProviderId); } +// This struct is here to support the forEachCard function call, defined in decklist. +// It requires a function to be called for each card, and it will set the providerId. +struct ClearSetNameNumberAndProviderId +{ + // Main operator for struct, allowing the foreachcard to work. + ClearSetNameNumberAndProviderId() + { + } + + void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const + { + Q_UNUSED(node); + // Set the providerId on the card + card->setCardSetShortName(nullptr); + card->setCardCollectorNumber(nullptr); + card->setCardProviderId(nullptr); + } +}; + /** * Clears the set name and numbers on each card in the decklist. */ diff --git a/cockatrice/src/deck/deck_loader.h b/cockatrice/src/deck/deck_loader.h index 53dd1f711..cf83f24fb 100644 --- a/cockatrice/src/deck/deck_loader.h +++ b/cockatrice/src/deck/deck_loader.h @@ -78,6 +78,7 @@ public: bool saveToFile(const QString &fileName, FileFormat fmt); bool updateLastLoadedTimestamp(const QString &fileName, FileFormat fmt); QString exportDeckToDecklist(DecklistWebsite website); + void setProviderIdToPreferredPrinting(); void resolveSetNameAndNumberToProviderID(); diff --git a/cockatrice/src/dialogs/dlg_select_set_for_cards.cpp b/cockatrice/src/dialogs/dlg_select_set_for_cards.cpp new file mode 100644 index 000000000..1dea30e89 --- /dev/null +++ b/cockatrice/src/dialogs/dlg_select_set_for_cards.cpp @@ -0,0 +1,642 @@ +#include "dlg_select_set_for_cards.h" + +#include "../client/ui/widgets/cards/card_info_picture_widget.h" +#include "../client/ui/widgets/general/layout_containers/flow_widget.h" +#include "../deck/deck_loader.h" +#include "../game/cards/card_database_manager.h" +#include "dlg_select_set_for_cards.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DlgSelectSetForCards::DlgSelectSetForCards(QWidget *parent, DeckListModel *_model) : QDialog(parent), model(_model) +{ + setMinimumSize(500, 500); + setAcceptDrops(true); + + instructionLabel = new QLabel(this); + instructionLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + + QVBoxLayout *mainLayout = new QVBoxLayout(this); + setLayout(mainLayout); + + // Main vertical splitter + QSplitter *splitter = new QSplitter(Qt::Vertical, this); + + // Top scroll area + scrollArea = new QScrollArea(this); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + scrollArea->setWidgetResizable(true); + + listContainer = new QWidget(scrollArea); + listLayout = new QVBoxLayout(listContainer); + listContainer->setLayout(listLayout); + scrollArea->setWidget(listContainer); + + // Bottom horizontal splitter + QSplitter *bottomSplitter = new QSplitter(Qt::Horizontal, this); + + // Left container + QWidget *leftContainer = new QWidget(this); + QVBoxLayout *leftLayout = new QVBoxLayout(leftContainer); + leftLayout->setContentsMargins(0, 0, 0, 0); + + uneditedCardsLabel = new QLabel(this); + uneditedCardsArea = new QScrollArea(this); + uneditedCardsArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + uneditedCardsArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + uneditedCardsArea->setWidgetResizable(true); + + uneditedCardsFlowWidget = + new FlowWidget(uneditedCardsArea, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); + uneditedCardsArea->setWidget(uneditedCardsFlowWidget); + + leftLayout->addWidget(uneditedCardsLabel); + leftLayout->addWidget(uneditedCardsArea); + leftContainer->setLayout(leftLayout); + + // Right container + QWidget *rightContainer = new QWidget(this); + QVBoxLayout *rightLayout = new QVBoxLayout(rightContainer); + rightLayout->setContentsMargins(0, 0, 0, 0); + + modifiedCardsLabel = new QLabel(this); + modifiedCardsArea = new QScrollArea(this); + modifiedCardsArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + modifiedCardsArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + modifiedCardsArea->setWidgetResizable(true); + + modifiedCardsFlowWidget = + new FlowWidget(modifiedCardsArea, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); + modifiedCardsArea->setWidget(modifiedCardsFlowWidget); + + rightLayout->addWidget(modifiedCardsLabel); + rightLayout->addWidget(modifiedCardsArea); + rightContainer->setLayout(rightLayout); + + // Add left and right containers to the bottom splitter + bottomSplitter->addWidget(leftContainer); + bottomSplitter->addWidget(rightContainer); + + // Add widgets to the main splitter + splitter->addWidget(scrollArea); + splitter->addWidget(bottomSplitter); + + cardsForSets = getCardsForSets(); + + sortSetsByCount(); + updateCardLists(); + + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(actOK())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + + clearButton = new QPushButton(buttonBox); + connect(clearButton, &QPushButton::clicked, this, &DlgSelectSetForCards::actClear); + + setAllToPreferredButton = new QPushButton(buttonBox); + connect(setAllToPreferredButton, &QPushButton::clicked, this, &DlgSelectSetForCards::actSetAllToPreferred); + + buttonBox->addButton(clearButton, QDialogButtonBox::ActionRole); + buttonBox->addButton(setAllToPreferredButton, QDialogButtonBox::ActionRole); + + // Set stretch factors + splitter->setStretchFactor(0, 6); // Scroll area gets more space + splitter->setStretchFactor(1, 2); // Bottom part gets less space + splitter->setStretchFactor(2, 1); // Buttons take minimal space + + bottomSplitter->setStretchFactor(0, 1); // Left and right equally split + bottomSplitter->setStretchFactor(1, 1); + + connect(this, &DlgSelectSetForCards::orderChanged, this, &DlgSelectSetForCards::updateLayoutOrder); + connect(this, &DlgSelectSetForCards::widgetOrderChanged, this, &DlgSelectSetForCards::updateCardLists); + + mainLayout->addWidget(instructionLabel); + mainLayout->addWidget(splitter); + mainLayout->addWidget(buttonBox); + + retranslateUi(); + setWindowFlags(Qt::Window); + showMaximized(); +} + +void DlgSelectSetForCards::retranslateUi() +{ + uneditedCardsLabel->setText(tr("Unmodified Cards:")); + modifiedCardsLabel->setText(tr("Modified Cards:")); + instructionLabel->setText(tr("Check Sets to enable them. Drag-and-Drop to reorder them and change their " + "priority. Cards will use the printing of the highest priority enabled set.")); + clearButton->setText(tr("Clear all set information")); + setAllToPreferredButton->setText(tr("Set all to preferred")); +} + +void DlgSelectSetForCards::actOK() +{ + QMap modifiedSetsAndCardsMap = getModifiedCards(); + for (QString modifiedSet : modifiedSetsAndCardsMap.keys()) { + for (QString card : modifiedSetsAndCardsMap.value(modifiedSet)) { + QModelIndex find_card = model->findCard(card, DECK_ZONE_MAIN); + if (!find_card.isValid()) { + continue; + } + model->removeRow(find_card.row(), find_card.parent()); + model->addCard(card, CardDatabaseManager::getInstance()->getSpecificSetForCard(card, modifiedSet, ""), + DECK_ZONE_MAIN); + } + } + accept(); +} + +void DlgSelectSetForCards::actClear() +{ + model->getDeckList()->clearSetNamesAndNumbers(); + accept(); +} + +void DlgSelectSetForCards::actSetAllToPreferred() +{ + model->getDeckList()->clearSetNamesAndNumbers(); + model->getDeckList()->setProviderIdToPreferredPrinting(); + accept(); +} + +void DlgSelectSetForCards::sortSetsByCount() +{ + QMap setsForCards = getSetsForCards(); + + // Convert map to a sortable list + QVector> setList; + for (auto it = setsForCards.begin(); it != setsForCards.end(); ++it) { + setList.append(qMakePair(it.key(), it.value())); + } + + // Sort in descending order of count + std::sort(setList.begin(), setList.end(), + [](const QPair &a, const QPair &b) { return a.second > b.second; }); + + // Clear existing entries + qDeleteAll(setEntries); + setEntries.clear(); + + // Populate with sorted entries + for (const auto &entry : setList) { + SetEntryWidget *widget = new SetEntryWidget(this, entry.first, entry.second); + listLayout->addWidget(widget); + setEntries.insert(entry.first, widget); + } +} + +QMap DlgSelectSetForCards::getSetsForCards() +{ + QMap setCounts; + if (!model) + return setCounts; + + DeckList *decklist = model->getDeckList(); + if (!decklist) + return setCounts; + + InnerDecklistNode *listRoot = decklist->getRoot(); + if (!listRoot) + return setCounts; + + for (auto *i : *listRoot) { + auto *countCurrentZone = dynamic_cast(i); + if (!countCurrentZone) + continue; + + for (auto *cardNode : *countCurrentZone) { + auto *currentCard = dynamic_cast(cardNode); + if (!currentCard) + continue; + + CardInfoPtr infoPtr = CardDatabaseManager::getInstance()->getCard(currentCard->getName()); + if (!infoPtr) + continue; + + CardInfoPerSetMap infoPerSetMap = infoPtr->getSets(); + for (auto it = infoPerSetMap.begin(); it != infoPerSetMap.end(); ++it) { + setCounts[it.key()]++; + } + } + } + return setCounts; +} + +void DlgSelectSetForCards::updateCardLists() +{ + for (SetEntryWidget *entryWidget : entry_widgets) { + entryWidget->populateCardList(); + if (entryWidget->expanded) { + entryWidget->updateCardDisplayWidgets(); + } + entryWidget->checkVisibility(); + } + + uneditedCardsFlowWidget->clearLayout(); + modifiedCardsFlowWidget->clearLayout(); + + // Map from set name to a set of selected cards in that set + QMap> selectedCardsBySet; + for (SetEntryWidget *entryWidget : entry_widgets) { + if (entryWidget->isChecked()) { + QStringList cardsInSet = entryWidget->getAllCardsForSet(); + QSet cardSet = QSet(cardsInSet.begin(), cardsInSet.end()); // Convert list to set + selectedCardsBySet.insert(entryWidget->setName, cardSet); + } + } + + DeckList *decklist = model->getDeckList(); + if (!decklist) + return; + + InnerDecklistNode *listRoot = decklist->getRoot(); + if (!listRoot) + return; + + for (auto *i : *listRoot) { + auto *countCurrentZone = dynamic_cast(i); + if (!countCurrentZone) + continue; + + for (auto *cardNode : *countCurrentZone) { + auto *currentCard = dynamic_cast(cardNode); + if (!currentCard) + continue; + + bool found = false; + QString foundSetName; + + // Check across all sets if the card is present + for (auto it = selectedCardsBySet.begin(); it != selectedCardsBySet.end(); ++it) { + if (it.value().contains(currentCard->getName())) { + found = true; + foundSetName = it.key(); // Store the set name where it was found + break; // Stop at the first match + } + } + + if (!found) { + // The card was not in any selected set + CardInfoPtr infoPtr = CardDatabaseManager::getInstance()->getCard(currentCard->getName()); + CardInfoPictureWidget *picture_widget = new CardInfoPictureWidget(uneditedCardsFlowWidget); + picture_widget->setCard(infoPtr); + uneditedCardsFlowWidget->addWidget(picture_widget); + } else { + CardInfoPtr infoPtr = CardDatabaseManager::getInstance()->getCardByNameAndProviderId( + currentCard->getName(), CardDatabaseManager::getInstance() + ->getSpecificSetForCard(currentCard->getName(), foundSetName, "") + .getProperty("uuid")); + CardInfoPictureWidget *picture_widget = new CardInfoPictureWidget(modifiedCardsFlowWidget); + picture_widget->setCard(infoPtr); + modifiedCardsFlowWidget->addWidget(picture_widget); + } + } + } +} + +void DlgSelectSetForCards::dragEnterEvent(QDragEnterEvent *event) +{ + if (event->mimeData()->hasFormat("application/x-setentrywidget")) { + // Highlight the drop target area + event->acceptProposedAction(); + // Optionally, change cursor to indicate a valid drop area + setCursor(Qt::OpenHandCursor); + } +} + +void DlgSelectSetForCards::dropEvent(QDropEvent *event) +{ + QByteArray itemData = event->mimeData()->data("application/x-setentrywidget"); + QString draggedSetName = QString::fromUtf8(itemData); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + QPoint adjustedPos = event->position().toPoint() + QPoint(0, scrollArea->verticalScrollBar()->value()); +#else + QPoint adjustedPos = event->pos() + QPoint(0, scrollArea->verticalScrollBar()->value()); +#endif + int dropIndex = -1; + for (int i = 0; i < listLayout->count(); ++i) { + QWidget *widget = listLayout->itemAt(i)->widget(); + if (widget && widget->geometry().contains(adjustedPos)) { + dropIndex = i; + break; + } + } + + if (dropIndex != -1) { + // Find the dragged widget and move it to the new position + SetEntryWidget *draggedWidget = setEntries.value(draggedSetName, nullptr); + if (draggedWidget) { + listLayout->removeWidget(draggedWidget); + listLayout->insertWidget(dropIndex, draggedWidget); + } + } + + event->acceptProposedAction(); + // Reset cursor after drop + unsetCursor(); + + // We need to execute this AFTER the current event-cycle so we use a timer. + QTimer::singleShot(10, this, [this]() { emit orderChanged(); }); +} + +QMap DlgSelectSetForCards::getCardsForSets() +{ + QMap setCards; + if (!model) + return setCards; + + DeckList *decklist = model->getDeckList(); + if (!decklist) + return setCards; + + InnerDecklistNode *listRoot = decklist->getRoot(); + if (!listRoot) + return setCards; + + for (auto *i : *listRoot) { + auto *countCurrentZone = dynamic_cast(i); + if (!countCurrentZone) + continue; + + for (auto *cardNode : *countCurrentZone) { + auto *currentCard = dynamic_cast(cardNode); + if (!currentCard) + continue; + + CardInfoPtr infoPtr = CardDatabaseManager::getInstance()->getCard(currentCard->getName()); + if (!infoPtr) + continue; + + CardInfoPerSetMap infoPerSetMap = infoPtr->getSets(); + for (auto it = infoPerSetMap.begin(); it != infoPerSetMap.end(); ++it) { + setCards[it.key()].append(currentCard->getName()); + } + } + } + return setCards; +} + +QMap DlgSelectSetForCards::getModifiedCards() +{ + QMap modifiedCards; + for (int i = 0; i < listLayout->count(); ++i) { + QWidget *widget = listLayout->itemAt(i)->widget(); + if (auto entry = qobject_cast(widget)) { + if (entry->isChecked()) { + QStringList cardsInSet = entry->getAllCardsForSet(); + + for (QString cardInSet : cardsInSet) { + bool alreadyContained = false; + for (QString key : modifiedCards.keys()) { + if (modifiedCards[key].contains(cardInSet)) { + alreadyContained = true; + } + } + if (!alreadyContained) { + modifiedCards[entry->setName].append(cardInSet); + } + } + } + } + } + return modifiedCards; +} + +void DlgSelectSetForCards::updateLayoutOrder() +{ + entry_widgets.clear(); + for (int i = 0; i < listLayout->count(); ++i) { + QWidget *widget = listLayout->itemAt(i)->widget(); + if (auto entry = qobject_cast(widget)) { + entry_widgets.append(entry); + } + } + + emit widgetOrderChanged(); +} + +SetEntryWidget::SetEntryWidget(DlgSelectSetForCards *_parent, const QString &_setName, int count) + : QWidget(_parent), parent(_parent), setName(_setName), expanded(false) +{ + layout = new QVBoxLayout(this); + setLayout(layout); + + QHBoxLayout *headerLayout = new QHBoxLayout(); + CardSetPtr set = CardDatabaseManager::getInstance()->getSet(setName); + checkBox = new QCheckBox("(" + set->getShortName() + ") - " + set->getLongName(), this); + connect(checkBox, &QCheckBox::toggled, parent, &DlgSelectSetForCards::updateLayoutOrder); + expandButton = new QPushButton("+", this); + countLabel = new QLabel(QString::number(count), this); + + connect(expandButton, &QPushButton::clicked, this, &SetEntryWidget::toggleExpansion); + + headerLayout->addWidget(checkBox); + headerLayout->addWidget(countLabel); + headerLayout->addWidget(expandButton); + layout->addLayout(headerLayout); + + possibleCardsLabel = new QLabel(this); + possibleCardsLabel->setText("Unselected cards in set:"); + possibleCardsLabel->hide(); + + cardListContainer = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff); + cardListContainer->hide(); + + alreadySelectedCardsLabel = new QLabel(this); + alreadySelectedCardsLabel->setText("Cards in set already selected in higher priority set:"); + alreadySelectedCardsLabel->hide(); + + alreadySelectedCardListContainer = + new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff); + alreadySelectedCardListContainer->hide(); + + layout->addWidget(possibleCardsLabel); + layout->addWidget(cardListContainer); + layout->addWidget(alreadySelectedCardsLabel); + layout->addWidget(alreadySelectedCardListContainer); + setAttribute(Qt::WA_DeleteOnClose, false); + setAttribute(Qt::WA_StyledBackground, true); +} + +void SetEntryWidget::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + // Create a drag object and set up mime data + QDrag *drag = new QDrag(this); + QMimeData *mimeData = new QMimeData; + + // Set the mime data to store the dragged set's name + mimeData->setData("application/x-setentrywidget", setName.toUtf8()); + drag->setMimeData(mimeData); + + // Create a "ghost" pixmap to represent the widget during dragging + QPixmap pixmap = this->grab(); + + // Ensure pixmap has a transparent background + QImage image = pixmap.toImage(); + for (int y = 0; y < image.height(); ++y) { + for (int x = 0; x < image.width(); ++x) { + if (image.pixel(x, y) == Qt::transparent) { + image.setPixel(x, y, QColor(0, 0, 0, 0).rgba()); // Set transparency where needed + } + } + } + + pixmap = QPixmap::fromImage(image); // Convert back to pixmap after transparency manipulation + + // Set the pixmap for the drag object + drag->setPixmap(pixmap); + + // Optionally adjust the pixmap position offset to align with the cursor + drag->setHotSpot(event->pos()); + + drag->exec(Qt::MoveAction); + } +} + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +void SetEntryWidget::enterEvent(QEnterEvent *event) +#else +void SetEntryWidget::enterEvent(QEvent *event) +#endif +{ + QWidget::enterEvent(event); // Call the base class handler + // Highlight the widget by changing the background color only for the widget itself + setStyleSheet("SetEntryWidget { background: gray; }"); + + // Change cursor to open hand + setCursor(Qt::OpenHandCursor); + repaint(); +} + +void SetEntryWidget::leaveEvent(QEvent *event) +{ + QWidget::leaveEvent(event); // Call the base class handler + // Reset the background color only for the widget itself + setStyleSheet("SetEntryWidget { background: none; }"); + + // Reset cursor to default + setCursor(Qt::ArrowCursor); + repaint(); +} + +void SetEntryWidget::dragMoveEvent(QDragMoveEvent *event) +{ + // Check if the mime data is of the correct type + if (event->mimeData()->hasFormat("application/x-setentrywidget")) { + setCursor(Qt::ClosedHandCursor); // Hand cursor to indicate move + // Get the current position of the widget being dragged + + // For now, we will just highlight the widget when dragged. + QPainter painter(this); + QColor highlightColor(255, 255, 255, 128); // Semi-transparent white + painter.setBrush(QBrush(highlightColor)); + painter.setPen(Qt::NoPen); + painter.drawRect(this->rect()); // Highlight the widget area + + // Allow the widget to be moved to the new position + event->acceptProposedAction(); + } else { + event->ignore(); + } +} + +bool SetEntryWidget::isChecked() const +{ + return checkBox->isChecked(); +} + +void SetEntryWidget::toggleExpansion() +{ + expanded = !expanded; + possibleCardsLabel->setVisible(expanded); + cardListContainer->setVisible(expanded); + alreadySelectedCardsLabel->setVisible(expanded); + alreadySelectedCardListContainer->setVisible(expanded); + expandButton->setText(expanded ? "-" : "+"); + + populateCardList(); + updateCardDisplayWidgets(); + + parent->updateCardLists(); +} + +void SetEntryWidget::checkVisibility() +{ + if (possibleCards.empty()) { + setHidden(true); + } else { + setVisible(true); + } +} + +QStringList SetEntryWidget::getAllCardsForSet() +{ + QStringList list; + QMap setCards = parent->cardsForSets; + if (setCards.contains(setName)) { + for (const QString &cardName : setCards[setName]) { + list << cardName; + } + } + return list; +} + +void SetEntryWidget::populateCardList() +{ + possibleCards = getAllCardsForSet(); + + for (SetEntryWidget *entryWidget : parent->entry_widgets) { + if (entryWidget == this) { + break; + } + if (entryWidget->isChecked()) { + for (const QString &cardName : entryWidget->possibleCards) { + possibleCards.removeAll(cardName); + } + } + } + + unusedCards = getAllCardsForSet(); + for (const QString &cardName : possibleCards) { + unusedCards.removeAll(cardName); + } + checkVisibility(); + countLabel->setText(QString::number(possibleCards.size()) + " (" + + QString::number(possibleCards.size() + unusedCards.size()) + ")"); +} + +void SetEntryWidget::updateCardDisplayWidgets() +{ + cardListContainer->clearLayout(); + alreadySelectedCardListContainer->clearLayout(); + + for (const QString &cardName : possibleCards) { + CardInfoPictureWidget *picture_widget = new CardInfoPictureWidget(cardListContainer); + picture_widget->setCard(CardDatabaseManager::getInstance()->getCardByNameAndProviderId( + cardName, + CardDatabaseManager::getInstance()->getSpecificSetForCard(cardName, setName, nullptr).getProperty("uuid"))); + cardListContainer->addWidget(picture_widget); + } + + for (const QString &cardName : unusedCards) { + CardInfoPictureWidget *picture_widget = new CardInfoPictureWidget(alreadySelectedCardListContainer); + picture_widget->setCard(CardDatabaseManager::getInstance()->getCardByNameAndProviderId( + cardName, + CardDatabaseManager::getInstance()->getSpecificSetForCard(cardName, setName, nullptr).getProperty("uuid"))); + alreadySelectedCardListContainer->addWidget(picture_widget); + } +} diff --git a/cockatrice/src/dialogs/dlg_select_set_for_cards.h b/cockatrice/src/dialogs/dlg_select_set_for_cards.h new file mode 100644 index 000000000..11d82ba7e --- /dev/null +++ b/cockatrice/src/dialogs/dlg_select_set_for_cards.h @@ -0,0 +1,104 @@ +#ifndef DLG_SELECT_SET_FOR_CARDS_H +#define DLG_SELECT_SET_FOR_CARDS_H + +#include "../client/ui/widgets/general/layout_containers/flow_widget.h" +#include "../deck/deck_list_model.h" + +#include +#include +#include +#include +#include +#include +#include + +class SetEntryWidget; // Forward declaration + +class DlgSelectSetForCards : public QDialog +{ + Q_OBJECT + +public: + explicit DlgSelectSetForCards(QWidget *parent, DeckListModel *_model); + void retranslateUi(); + void sortSetsByCount(); + QMap getCardsForSets(); + QMap getModifiedCards(); + QVBoxLayout *listLayout; + QList entry_widgets; + QMap cardsForSets; + +signals: + void widgetOrderChanged(); + void orderChanged(); + +public slots: + void actOK(); + void actClear(); + void actSetAllToPreferred(); + void updateLayoutOrder(); + void updateCardLists(); + void dragEnterEvent(QDragEnterEvent *event) override; + void dropEvent(QDropEvent *event) override; + +private: + QVBoxLayout *layout; + QLabel *instructionLabel; + QScrollArea *scrollArea; + QScrollArea *uneditedCardsArea; + FlowWidget *uneditedCardsFlowWidget; + QLabel *uneditedCardsLabel; + QScrollArea *modifiedCardsArea; + FlowWidget *modifiedCardsFlowWidget; + QLabel *modifiedCardsLabel; + QWidget *listContainer; + QListWidget *listWidget; + DeckListModel *model; + QMap setEntries; + QPushButton *clearButton; + QPushButton *setAllToPreferredButton; + + QMap getSetsForCards(); +}; + +class SetEntryWidget : public QWidget +{ + Q_OBJECT + +public: + explicit SetEntryWidget(DlgSelectSetForCards *parent, const QString &setName, int count); + void toggleExpansion(); + void checkVisibility(); + QStringList getAllCardsForSet(); + void populateCardList(); + void updateCardDisplayWidgets(); + bool isChecked() const; + DlgSelectSetForCards *parent; + QString setName; + bool expanded; + +public slots: + void mousePressEvent(QMouseEvent *event) override; +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + void enterEvent(QEnterEvent *event) override; +#else + void enterEvent(QEvent *event) override; +#endif + void leaveEvent(QEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + +private: + QVBoxLayout *layout; + QCheckBox *checkBox; + QPushButton *expandButton; + QLabel *countLabel; + QLabel *possibleCardsLabel; + FlowWidget *cardListContainer; + QLabel *alreadySelectedCardsLabel; + FlowWidget *alreadySelectedCardListContainer; + QVBoxLayout *cardListLayout; + QStringList possibleCards; + QStringList unusedCards; +}; + +#endif // DLG_SELECT_SET_FOR_CARDS_H \ No newline at end of file diff --git a/cockatrice/src/game/cards/card_database.cpp b/cockatrice/src/game/cards/card_database.cpp index 396a32a10..504054bbe 100644 --- a/cockatrice/src/game/cards/card_database.cpp +++ b/cockatrice/src/game/cards/card_database.cpp @@ -384,8 +384,13 @@ CardInfoPerSet CardDatabase::getSpecificSetForCard(const QString &cardName, for (const auto &cardInfoPerSetList : setMap) { for (auto &cardInfoForSet : cardInfoPerSetList) { - if (cardInfoForSet.getPtr()->getShortName() == setShortName) { - if (cardInfoForSet.getProperty("num") == collectorNumber || collectorNumber.isEmpty()) { + if (collectorNumber != nullptr) { + if (cardInfoForSet.getPtr()->getShortName() == setShortName && + cardInfoForSet.getProperty("num") == collectorNumber) { + return cardInfoForSet; + } + } else { + if (cardInfoForSet.getPtr()->getShortName() == setShortName) { return cardInfoForSet; } } diff --git a/common/decklist.cpp b/common/decklist.cpp index e72e343b5..a9a825592 100644 --- a/common/decklist.cpp +++ b/common/decklist.cpp @@ -166,13 +166,16 @@ AbstractDecklistNode *InnerDecklistNode::findCardChildByNameProviderIdAndNumber( const QString &_cardNumber) { for (const auto &i : *this) { - if (i != nullptr && i->getName() == _name) { - if (i->getCardCollectorNumber() == _cardNumber) { - if (i->getCardProviderId() == _providerId) { - return i; - } - } + if (!i || i->getName() != _name) { + continue; } + if (_cardNumber != "" && i->getCardCollectorNumber() != _cardNumber) { + continue; + } + if (_providerId != "" && i->getCardProviderId() != _providerId) { + continue; + } + return i; } return nullptr; } diff --git a/common/decklist.h b/common/decklist.h index 1db3b3991..d7ed4add4 100644 --- a/common/decklist.h +++ b/common/decklist.h @@ -137,7 +137,7 @@ public: void clearTree(); AbstractDecklistNode *findChild(const QString &_name); AbstractDecklistNode *findCardChildByNameProviderIdAndNumber(const QString &_name, - const QString &_providerId, + const QString &_providerId = "", const QString &_cardNumber = ""); int height() const override; int recursiveCount(bool countTotalCards = false) const;