Deck format legality checker (#6166)

* Deck legality checker.

Took 51 seconds

Took 1 minute

Took 1 minute

Took 5 minutes

Took 3 minutes

* Adjust format parsing.

Took 8 minutes


Took 3 seconds

* toString() the xmlName

Took 4 minutes

* more toStrings()

Took 5 minutes

* Comments

Took 3 minutes

* Layout

Took 2 minutes

* Layout part 2: Electric boogaloo

Took 59 seconds

* Update cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp

Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>

* Move layout.

Took 4 minutes


Took 10 seconds

* Emit deckModified

Took 6 minutes

* Fix qOverloads

Took 4 minutes

* Fix qOverloads

Took 12 seconds

* Consider text and name in a special way.

Took 11 minutes

* Adjust "Any number of" oracle text

Took 5 minutes

* Store allowedCounts by format

Took 15 minutes

Took 6 seconds

* Only restrict vintage.

Took 2 minutes

* Adjust for DBConverter.

Took 6 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>
This commit is contained in:
BruebachL 2025-12-13 15:17:55 +01:00 committed by GitHub
parent 2e2682aad4
commit ccdda39e78
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 987 additions and 35 deletions

View file

@ -124,6 +124,12 @@ void DeckEditorDeckDockWidget::createDeckDock()
quickSettingsWidget->addSettingsWidget(showBannerCardCheckBox);
quickSettingsWidget->addSettingsWidget(showTagsWidgetCheckBox);
formatLabel = new QLabel(this);
formatComboBox = new QComboBox(this);
formatComboBox->addItem(tr("Loading Database..."));
formatComboBox->setEnabled(false); // Disable until loaded
commentsLabel = new QLabel();
commentsLabel->setObjectName("commentsLabel");
commentsEdit = new QTextEdit;
@ -208,13 +214,16 @@ void DeckEditorDeckDockWidget::createDeckDock()
upperLayout->addWidget(commentsLabel, 1, 0);
upperLayout->addWidget(commentsEdit, 1, 1);
upperLayout->addWidget(bannerCardLabel, 2, 0);
upperLayout->addWidget(bannerCardComboBox, 2, 1);
upperLayout->addWidget(formatLabel, 2, 0);
upperLayout->addWidget(formatComboBox, 2, 1);
upperLayout->addWidget(deckTagsDisplayWidget, 3, 1);
upperLayout->addWidget(bannerCardLabel, 3, 0);
upperLayout->addWidget(bannerCardComboBox, 3, 1);
upperLayout->addWidget(activeGroupCriteriaLabel, 4, 0);
upperLayout->addWidget(activeGroupCriteriaComboBox, 4, 1);
upperLayout->addWidget(deckTagsDisplayWidget, 4, 1);
upperLayout->addWidget(activeGroupCriteriaLabel, 5, 0);
upperLayout->addWidget(activeGroupCriteriaComboBox, 5, 1);
hashLabel1 = new QLabel();
hashLabel1->setObjectName("hashLabel1");
@ -263,6 +272,46 @@ void DeckEditorDeckDockWidget::createDeckDock()
refreshShortcuts();
retranslateUi();
connect(CardDatabaseManager::getInstance(), &CardDatabase::cardDatabaseLoadingFinished, this,
&DeckEditorDeckDockWidget::initializeFormats);
if (CardDatabaseManager::getInstance()->getLoadStatus() == LoadStatus::Ok) {
initializeFormats();
}
}
void DeckEditorDeckDockWidget::initializeFormats()
{
QMap<QString, int> allFormats = CardDatabaseManager::query()->getAllFormatsWithCount();
formatComboBox->clear(); // Remove "Loading Database..."
formatComboBox->setEnabled(true);
// Populate with formats
formatComboBox->addItem("", "");
for (auto it = allFormats.constBegin(); it != allFormats.constEnd(); ++it) {
QString displayText = QString("%1").arg(it.key());
formatComboBox->addItem(displayText, it.key()); // store the raw key in itemData
}
if (!deckModel->getDeckList()->getGameFormat().isEmpty()) {
deckModel->setActiveFormat(deckModel->getDeckList()->getGameFormat());
formatComboBox->setCurrentIndex(formatComboBox->findData(deckModel->getDeckList()->getGameFormat()));
} else {
// Ensure no selection is visible initially
formatComboBox->setCurrentIndex(-1);
}
connect(formatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) {
if (index >= 0) {
QString formatKey = formatComboBox->itemData(index).toString();
deckModel->setActiveFormat(formatKey);
} else {
deckModel->setActiveFormat(QString()); // clear format if deselected
}
emit deckModified();
});
}
ExactCard DeckEditorDeckDockWidget::getCurrentCard()
@ -466,6 +515,12 @@ void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel()
void DeckEditorDeckDockWidget::sortDeckModelToDeckView()
{
deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder());
deckModel->setActiveFormat(deckModel->getDeckList()->getGameFormat());
formatComboBox->setCurrentIndex(formatComboBox->findData(deckModel->getDeckList()->getGameFormat()));
deckView->expandAll();
deckView->expandAll();
emit deckChanged();
}
DeckLoader *DeckEditorDeckDockWidget::getDeckLoader()
@ -724,6 +779,8 @@ void DeckEditorDeckDockWidget::retranslateUi()
showTagsWidgetCheckBox->setText(tr("Show tags selection menu"));
commentsLabel->setText(tr("&Comments:"));
activeGroupCriteriaLabel->setText(tr("Group by:"));
formatLabel->setText(tr("Format:"));
hashLabel1->setText(tr("Hash:"));
aIncrement->setText(tr("&Increment number"));

View file

@ -69,6 +69,7 @@ public slots:
void actSwapCard();
void actRemoveCard();
void offsetCountAtIndex(const QModelIndex &idx, int offset);
void initializeFormats();
void expandAll();
signals:
@ -100,6 +101,8 @@ private:
LineEditUnfocusable *hashLabel;
QLabel *activeGroupCriteriaLabel;
QComboBox *activeGroupCriteriaComboBox;
QLabel *formatLabel;
QComboBox *formatComboBox;
QAction *aRemoveCard, *aIncrement, *aDecrement, *aSwapCard;

View file

@ -23,8 +23,7 @@ QVariant DeckListStyleProxy::data(const QModelIndex &index, int role) const
if (role == Qt::BackgroundRole) {
if (isCard) {
const bool legal =
true; // TODO: Not implemented yet. QIdentityProxyModel::data(index, DeckRoles::IsLegalRole).toBool();
const bool legal = QIdentityProxyModel::data(index, DeckRoles::IsLegalRole).toBool();
int base = 255 - (index.row() % 2) * 30;
return legal ? QBrush(QColor(base, base, base)) : QBrush(QColor(255, base / 3, base / 3));
} else {

View file

@ -214,7 +214,7 @@ TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor)
minDeckSizeLogicCombo->addItems({"Exact", "", ""}); // Exact = unset, ≥ = GTE, ≤ = LTE
minDeckSizeLogicCombo->setCurrentIndex(1); // default GTE
connect(minDeckSizeSpin, QOverload<int>::of(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch);
connect(minDeckSizeSpin, qOverload<int>(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch);
connect(minDeckSizeLogicCombo, &QComboBox::currentTextChanged, this, &TabArchidekt::doSearch);
// Page number
@ -224,7 +224,7 @@ TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor)
pageSpin->setRange(1, 9999);
pageSpin->setValue(1);
connect(pageSpin, QOverload<int>::of(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch);
connect(pageSpin, qOverload<int>(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch);
// Page display
currentPageDisplay = new QWidget(container);

View file

@ -0,0 +1,205 @@
#include "visual_database_display_format_legality_filter_widget.h"
#include "../../../filters/filter_tree_model.h"
#include <QPushButton>
#include <QSpinBox>
#include <QTimer>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/filters/filter_tree.h>
VisualDatabaseDisplayFormatLegalityFilterWidget::VisualDatabaseDisplayFormatLegalityFilterWidget(
QWidget *parent,
FilterTreeModel *_filterModel)
: QWidget(parent), filterModel(_filterModel)
{
allFormatsWithCount = CardDatabaseManager::query()->getAllFormatsWithCount();
setMaximumHeight(75);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
layout = new QHBoxLayout(this);
setLayout(layout);
layout->setContentsMargins(0, 1, 0, 1);
layout->setSpacing(1);
layout->setAlignment(Qt::AlignTop);
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
layout->addWidget(flowWidget);
// Create the spinbox
spinBox = new QSpinBox(this);
spinBox->setMinimum(1);
spinBox->setMaximum(getMaxMainTypeCount()); // Set the max value dynamically
spinBox->setValue(150);
layout->addWidget(spinBox);
connect(spinBox, qOverload<int>(&QSpinBox::valueChanged), this,
&VisualDatabaseDisplayFormatLegalityFilterWidget::updateFormatButtonsVisibility);
// Create the toggle button for Exact Match/Includes mode
toggleButton = new QPushButton(this);
toggleButton->setCheckable(true);
layout->addWidget(toggleButton);
connect(toggleButton, &QPushButton::toggled, this,
&VisualDatabaseDisplayFormatLegalityFilterWidget::updateFilterMode);
connect(filterModel, &FilterTreeModel::layoutChanged, this, [this]() {
QTimer::singleShot(100, this, &VisualDatabaseDisplayFormatLegalityFilterWidget::syncWithFilterModel);
});
createFormatButtons(); // Populate buttons initially
updateFilterMode(false); // Initialize toggle button text
retranslateUi();
}
void VisualDatabaseDisplayFormatLegalityFilterWidget::retranslateUi()
{
spinBox->setToolTip(tr("Do not display formats with less than this amount of cards in the database"));
toggleButton->setToolTip(tr("Filter mode (AND/OR/NOT conjunctions of filters)"));
}
void VisualDatabaseDisplayFormatLegalityFilterWidget::createFormatButtons()
{
// Iterate through main types and create buttons
for (auto it = allFormatsWithCount.begin(); it != allFormatsWithCount.end(); ++it) {
auto *button = new QPushButton(it.key(), flowWidget);
button->setCheckable(true);
button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }"
"QPushButton:checked { background-color: green; color: white; }");
flowWidget->addWidget(button);
formatButtons[it.key()] = button;
// Connect toggle signal
connect(button, &QPushButton::toggled, this,
[this, mainType = it.key()](bool checked) { handleFormatToggled(mainType, checked); });
}
updateFormatButtonsVisibility(); // Ensure visibility is updated initially
}
void VisualDatabaseDisplayFormatLegalityFilterWidget::updateFormatButtonsVisibility()
{
int threshold = spinBox->value(); // Get the current spinbox value
// Iterate through buttons and hide/disable those below the threshold
for (auto it = formatButtons.begin(); it != formatButtons.end(); ++it) {
bool visible = allFormatsWithCount[it.key()] >= threshold;
it.value()->setVisible(visible);
it.value()->setEnabled(visible);
}
}
int VisualDatabaseDisplayFormatLegalityFilterWidget::getMaxMainTypeCount() const
{
int maxCount = 1;
for (auto it = allFormatsWithCount.begin(); it != allFormatsWithCount.end(); ++it) {
maxCount = qMax(maxCount, it.value());
}
return maxCount;
}
void VisualDatabaseDisplayFormatLegalityFilterWidget::handleFormatToggled(const QString &format, bool active)
{
activeFormats[format] = active;
if (formatButtons.contains(format)) {
formatButtons[format]->setChecked(active);
}
updateFormatFilter();
}
void VisualDatabaseDisplayFormatLegalityFilterWidget::updateFormatFilter()
{
// Clear existing filters related to main type
filterModel->blockSignals(true);
filterModel->filterTree()->blockSignals(true);
filterModel->clearFiltersOfType(CardFilter::Attr::AttrFormat);
if (exactMatchMode) {
// Exact Match: Only selected main types are allowed
QSet<QString> selectedTypes;
for (const auto &type : activeFormats.keys()) {
if (activeFormats[type]) {
selectedTypes.insert(type);
}
}
if (!selectedTypes.isEmpty()) {
// Require all selected types (TypeAnd)
for (const auto &type : selectedTypes) {
QString typeString = type;
filterModel->addFilter(
new CardFilter(typeString, CardFilter::Type::TypeAnd, CardFilter::Attr::AttrFormat));
}
// Exclude any other types (TypeAndNot)
for (const auto &type : formatButtons.keys()) {
if (!selectedTypes.contains(type)) {
QString typeString = type;
filterModel->addFilter(
new CardFilter(typeString, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrFormat));
}
}
}
} else {
// Default Includes Mode (TypeOr) - match any selected main types
for (const auto &type : activeFormats.keys()) {
if (activeFormats[type]) {
QString typeString = type;
filterModel->addFilter(
new CardFilter(typeString, CardFilter::Type::TypeAnd, CardFilter::Attr::AttrFormat));
}
}
}
filterModel->blockSignals(false);
filterModel->filterTree()->blockSignals(false);
emit filterModel->filterTree()->changed();
emit filterModel->layoutChanged();
}
void VisualDatabaseDisplayFormatLegalityFilterWidget::updateFilterMode(bool checked)
{
exactMatchMode = checked;
toggleButton->setText(exactMatchMode ? tr("Mode: Exact Match") : tr("Mode: Includes"));
updateFormatFilter();
}
void VisualDatabaseDisplayFormatLegalityFilterWidget::syncWithFilterModel()
{
// Temporarily block signals for each button to prevent toggling while updating button states
for (auto it = formatButtons.begin(); it != formatButtons.end(); ++it) {
it.value()->blockSignals(true);
}
// Uncheck all buttons
for (auto it = formatButtons.begin(); it != formatButtons.end(); ++it) {
it.value()->setChecked(false);
}
// Get active filters for main types
QSet<QString> activeTypes;
for (const auto &filter : filterModel->getFiltersOfType(CardFilter::AttrFormat)) {
if (filter->type() == CardFilter::Type::TypeAnd) {
activeTypes.insert(filter->term());
}
}
// Check the buttons for active types
for (const auto &type : activeTypes) {
activeFormats[type] = true;
if (formatButtons.contains(type)) {
formatButtons[type]->setChecked(true);
}
}
// Re-enable signal emissions for each button
for (auto it = formatButtons.begin(); it != formatButtons.end(); ++it) {
it.value()->blockSignals(false);
}
// Update the visibility of buttons
updateFormatButtonsVisibility();
}

View file

@ -0,0 +1,43 @@
#ifndef COCKATRICE_VISUAL_DATABASE_DISPLAY_FORMAT_LEGALITY_FILTER_WIDGET_H
#define COCKATRICE_VISUAL_DATABASE_DISPLAY_FORMAT_LEGALITY_FILTER_WIDGET_H
#include "../../../filters/filter_tree_model.h"
#include "../general/layout_containers/flow_widget.h"
#include <QMap>
#include <QPushButton>
#include <QSpinBox>
#include <QToolButton>
#include <QVBoxLayout>
#include <QWidget>
class VisualDatabaseDisplayFormatLegalityFilterWidget : public QWidget
{
Q_OBJECT
public:
explicit VisualDatabaseDisplayFormatLegalityFilterWidget(QWidget *parent, FilterTreeModel *filterModel);
void retranslateUi();
void createFormatButtons();
void updateFormatButtonsVisibility();
int getMaxMainTypeCount() const;
void handleFormatToggled(const QString &format, bool active);
void updateFormatFilter();
void updateFilterMode(bool checked);
void syncWithFilterModel();
private:
FilterTreeModel *filterModel;
QMap<QString, int> allFormatsWithCount;
QSpinBox *spinBox;
QHBoxLayout *layout;
FlowWidget *flowWidget;
QPushButton *toggleButton; // Mode switch button
QMap<QString, bool> activeFormats; // Track active filters
QMap<QString, QPushButton *> formatButtons; // Store toggle buttons
bool exactMatchMode = false; // Toggle between "Exact Match" and "Includes"
};
#endif // COCKATRICE_VISUAL_DATABASE_DISPLAY_FORMAT_LEGALITY_FILTER_WIDGET_H

View file

@ -33,7 +33,7 @@ VisualDatabaseDisplayMainTypeFilterWidget::VisualDatabaseDisplayMainTypeFilterWi
spinBox->setMaximum(getMaxMainTypeCount()); // Set the max value dynamically
spinBox->setValue(150);
layout->addWidget(spinBox);
connect(spinBox, QOverload<int>::of(&QSpinBox::valueChanged), this,
connect(spinBox, qOverload<int>(&QSpinBox::valueChanged), this,
&VisualDatabaseDisplayMainTypeFilterWidget::updateMainTypeButtonsVisibility);
// Create the toggle button for Exact Match/Includes mode

View file

@ -27,7 +27,7 @@ VisualDatabaseDisplayRecentSetFilterSettingsWidget::VisualDatabaseDisplayRecentS
filterToMostRecentSetsAmount->setMaximum(100);
filterToMostRecentSetsAmount->setValue(
SettingsCache::instance().getVisualDatabaseDisplayFilterToMostRecentSetsAmount());
connect(filterToMostRecentSetsAmount, QOverload<int>::of(&QSpinBox::valueChanged), &SettingsCache::instance(),
connect(filterToMostRecentSetsAmount, qOverload<int>(&QSpinBox::valueChanged), &SettingsCache::instance(),
&SettingsCache::setVisualDatabaseDisplayFilterToMostRecentSetsAmount);
layout->addWidget(filterToMostRecentSetsCheckBox);

View file

@ -26,7 +26,7 @@ VisualDatabaseDisplaySubTypeFilterWidget::VisualDatabaseDisplaySubTypeFilterWidg
spinBox->setMaximum(getMaxSubTypeCount());
spinBox->setValue(150);
layout->addWidget(spinBox);
connect(spinBox, QOverload<int>::of(&QSpinBox::valueChanged), this,
connect(spinBox, qOverload<int>(&QSpinBox::valueChanged), this,
&VisualDatabaseDisplaySubTypeFilterWidget::updateSubTypeButtonsVisibility);
// Create search box

View file

@ -206,6 +206,7 @@ void VisualDatabaseDisplayWidget::initialize()
saveLoadWidget = new VisualDatabaseDisplayFilterSaveLoadWidget(this, filterModel);
nameFilterWidget = new VisualDatabaseDisplayNameFilterWidget(this, deckEditor, filterModel);
mainTypeFilterWidget = new VisualDatabaseDisplayMainTypeFilterWidget(this, filterModel);
formatLegalityWidget = new VisualDatabaseDisplayFormatLegalityFilterWidget(this, filterModel);
subTypeFilterWidget = new VisualDatabaseDisplaySubTypeFilterWidget(this, filterModel);
setFilterWidget = new VisualDatabaseDisplaySetFilterWidget(this, filterModel);
@ -223,6 +224,7 @@ void VisualDatabaseDisplayWidget::initialize()
filterContainerLayout->addWidget(quickFilterSubTypeWidget);
filterContainerLayout->addWidget(quickFilterSetWidget);
filterContainerLayout->addWidget(mainTypeFilterWidget);
filterContainerLayout->addWidget(formatLegalityWidget);
searchLayout->addWidget(colorFilterWidget);
searchLayout->addWidget(clearFilterWidget);

View file

@ -17,6 +17,7 @@
#include "../utility/custom_line_edit.h"
#include "visual_database_display_color_filter_widget.h"
#include "visual_database_display_filter_save_load_widget.h"
#include "visual_database_display_format_legality_filter_widget.h"
#include "visual_database_display_main_type_filter_widget.h"
#include "visual_database_display_name_filter_widget.h"
#include "visual_database_display_set_filter_widget.h"
@ -91,6 +92,7 @@ private:
SettingsButtonWidget *quickFilterNameWidget;
VisualDatabaseDisplayNameFilterWidget *nameFilterWidget;
VisualDatabaseDisplayMainTypeFilterWidget *mainTypeFilterWidget;
VisualDatabaseDisplayFormatLegalityFilterWidget *formatLegalityWidget;
SettingsButtonWidget *quickFilterSubTypeWidget;
VisualDatabaseDisplaySubTypeFilterWidget *subTypeFilterWidget;
SettingsButtonWidget *quickFilterSetWidget;

View file

@ -24,9 +24,9 @@ VisualDeckEditorSampleHandWidget::VisualDeckEditorSampleHandWidget(QWidget *pare
handSizeSpinBox = new QSpinBox(this);
handSizeSpinBox->setValue(SettingsCache::instance().getVisualDeckEditorSampleHandSize());
handSizeSpinBox->setMinimum(1);
connect(handSizeSpinBox, QOverload<int>::of(&QSpinBox::valueChanged), &SettingsCache::instance(),
connect(handSizeSpinBox, qOverload<int>(&QSpinBox::valueChanged), &SettingsCache::instance(),
&SettingsCache::setVisualDeckEditorSampleHandSize);
connect(handSizeSpinBox, QOverload<int>::of(&QSpinBox::valueChanged), this,
connect(handSizeSpinBox, qOverload<int>(&QSpinBox::valueChanged), this,
&VisualDeckEditorSampleHandWidget::updateDisplay);
resetAndHandSizeLayout->addWidget(handSizeSpinBox);

View file

@ -61,10 +61,10 @@ VisualDeckStorageQuickSettingsWidget::VisualDeckStorageQuickSettingsWidget(QWidg
unusedColorIdentitiesOpacitySpinBox->setMaximum(100);
unusedColorIdentitiesOpacitySpinBox->setValue(
SettingsCache::instance().getVisualDeckStorageUnusedColorIdentitiesOpacity());
connect(unusedColorIdentitiesOpacitySpinBox, QOverload<int>::of(&QSpinBox::valueChanged), this,
connect(unusedColorIdentitiesOpacitySpinBox, qOverload<int>(&QSpinBox::valueChanged), this,
&VisualDeckStorageQuickSettingsWidget::unusedColorIdentitiesOpacityChanged);
connect(unusedColorIdentitiesOpacitySpinBox, QOverload<int>::of(&QSpinBox::valueChanged),
&SettingsCache::instance(), &SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity);
connect(unusedColorIdentitiesOpacitySpinBox, qOverload<int>(&QSpinBox::valueChanged), &SettingsCache::instance(),
&SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity);
unusedColorIdentitiesOpacityLabel->setBuddy(unusedColorIdentitiesOpacitySpinBox);