Convert json data holder to structs, rename variables, extract widget

- Extract bracket estimation UI from DeckEditorDeckDockWidget into a
  new CommanderBracketWidget
- Move CommanderSpellbook integration settings from CardsDisplaySettings
  to DeckEditorSettings (matching the settings refactor on master)
- Rename CommanderSpellbook integration variables to drop the redundant
  'DeckEditor' prefix

Took 4 minutes

# Commit time for manual adjustment:
# Took 6 minutes

# Commit time for manual adjustment:
# Took 8 minutes
This commit is contained in:
Lukas Brübach 2026-08-08 23:19:58 +02:00
parent b0035d41cb
commit 375898c565
30 changed files with 575 additions and 568 deletions

View file

@ -372,6 +372,7 @@ set(cockatrice_SOURCES
src/interface/key_signals.cpp
src/interface/logger.cpp
src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_service.cpp
src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_widget.cpp
src/interface/widgets/tabs/api/commander_spellbook/handle_commander_brackets.cpp
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h

View file

@ -3,19 +3,14 @@
#include "../../../client/settings/cache_settings.h"
#include "../../../client/settings/shortcuts_settings.h"
#include "../settings_page/user_interface_settings_page.h"
#include "../tabs/api/commander_spellbook/commander_bracket_service.h"
#include "../tabs/api/commander_spellbook/commander_spellbook_api_accessor.h"
#include "../tabs/api/commander_spellbook/commander_spellbook_bracket_explainer.h"
#include "../tabs/api/commander_spellbook/commander_bracket_widget.h"
#include "deck_list_style_proxy.h"
#include "deck_state_manager.h"
#include <QComboBox>
#include <QDialogButtonBox>
#include <QDockWidget>
#include <QFormLayout>
#include <QHeaderView>
#include <QLabel>
#include <QMessageBox>
#include <QSplitter>
#include <QTextEdit>
#include <libcockatrice/card/database/card_database_manager.h>
@ -141,40 +136,7 @@ void DeckEditorDeckDockWidget::createDeckDock()
formatComboBox->addItem(tr("Loading Database..."));
formatComboBox->setEnabled(false); // Disable until loaded
// --- Commander bracket row (hidden, unless format is 'commander') ---
bracketLabel = new QLabel(tr("Bracket:"), this);
bracketValueLabel = new QLabel(this);
bracketValueLabel->setText("-");
bracketValueLabel->setObjectName("bracketValueLabel");
bracketInfoButton = new QToolButton(this);
bracketInfoButton->setText("?");
bracketInfoButton->setAutoRaise(true);
bracketInfoButton->setEnabled(false);
bracketRefreshButton = new QToolButton(this);
bracketRefreshButton->setIcon(QPixmap("theme:icons/reload"));
bracketRefreshButton->setAutoRaise(true);
connect(bracketRefreshButton, &QToolButton::clicked, this, &DeckEditorDeckDockWidget::requestBracketEstimate);
if (SettingsCache::instance().cardsDisplay().getDeckEditorCommanderSpellbookIntegrationEnabled() !=
deckEditorCommanderSpellbookIntegrationEnabledIndexUnprompted) {
connect(&SettingsCache::instance().cardsDisplay(),
&CardsDisplaySettings::deckEditorCommanderSpellbookIntegrationEnabledChanged, this,
&DeckEditorDeckDockWidget::maybeAutoEstimateBracket);
connect(&SettingsCache::instance().cardsDisplay(),
&CardsDisplaySettings::deckEditorCommanderSpellbookIntegrationUseOfficialBracketNamesChanged, this,
&DeckEditorDeckDockWidget::maybeAutoEstimateBracket);
}
bracketLabel->setVisible(false);
bracketValueLabel->setVisible(false);
bracketInfoButton->setVisible(false);
bracketRefreshButton->setVisible(false);
connect(&CommanderBracketService::instance(), &CommanderBracketService::estimateFinished, this,
&DeckEditorDeckDockWidget::onEstimateBracketFinished);
commanderBracketWidget = new CommanderBracketWidget(this);
commentsLabel = new QLabel();
commentsLabel->setObjectName("commentsLabel");
@ -261,15 +223,7 @@ void DeckEditorDeckDockWidget::createDeckDock()
upperLayout->addWidget(formatLabel, 2, 0);
upperLayout->addWidget(formatComboBox, 2, 1);
upperLayout->addWidget(bracketLabel, 3, 0);
auto *bracketRow = new QHBoxLayout;
bracketRow->addWidget(bracketValueLabel);
bracketRow->addWidget(bracketInfoButton);
bracketRow->addWidget(bracketRefreshButton);
bracketRow->addStretch();
upperLayout->addLayout(bracketRow, 3, 1);
upperLayout->addWidget(commanderBracketWidget, 3, 0, 1, 2);
upperLayout->addWidget(bannerCardLabel, 4, 0);
upperLayout->addWidget(bannerCardComboBox, 4, 1);
@ -335,151 +289,6 @@ void DeckEditorDeckDockWidget::createDeckDock()
}
}
bool DeckEditorDeckDockWidget::promptCommanderSpellbookIntegration()
{
QDialog dialog(this);
dialog.setWindowTitle(tr("CommanderSpellbook integration"));
auto *mainLayout = new QVBoxLayout(&dialog);
// Main text
auto *label = new QLabel(tr("CommanderSpellbook can analyze your deck and estimate its Commander bracket.\n\n"
"This sends your deck list to an external service.\n\n"
"CommanderSpellbook uses its own bracket naming system based on their own algorithm. "
"These names can be mapped to the official Commander brackets, but the mapping "
"is only an approximation."));
label->setWordWrap(true);
mainLayout->addWidget(label);
// Naming selector
auto *formLayout = new QFormLayout;
auto *namingCombo = new QComboBox(&dialog);
namingCombo->addItem(tr("CommanderSpellbook bracket names"));
namingCombo->addItem(tr("Official Commander bracket names (approximate)"));
namingCombo->setCurrentIndex(
SettingsCache::instance().cardsDisplay().getDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames()
? 1
: 0);
// Create label + explainer button
auto *labelWidget = new QWidget(&dialog);
auto *labelLayout = new QHBoxLayout(labelWidget);
labelLayout->setContentsMargins(0, 0, 0, 0);
auto *namingLabel = new QLabel(tr("Bracket naming:"), labelWidget);
auto *explainerButton = new QToolButton(labelWidget);
explainerButton->setText("?");
explainerButton->setAutoRaise(true);
explainerButton->setEnabled(false);
explainerButton->setToolTip(CommanderBracketNames::Explainer);
labelLayout->addWidget(namingLabel);
labelLayout->addWidget(explainerButton);
labelLayout->addStretch(); // push the button next to label, combo stays aligned
// Add row with the custom label widget
formLayout->addRow(labelWidget, namingCombo);
mainLayout->addLayout(formLayout);
// Buttons
auto *buttonBox = new QDialogButtonBox(&dialog);
auto *enableBtn = buttonBox->addButton(tr("Enable"), QDialogButtonBox::AcceptRole);
auto *automaticBtn = buttonBox->addButton(tr("Automatic"), QDialogButtonBox::ApplyRole);
auto *disableBtn = buttonBox->addButton(tr("Disable"), QDialogButtonBox::RejectRole);
mainLayout->addWidget(buttonBox);
// Track which button was clicked
QAbstractButton *clickedButton = nullptr;
QObject::connect(buttonBox, &QDialogButtonBox::clicked, &dialog, [&](QAbstractButton *btn) {
clickedButton = btn;
dialog.accept();
});
dialog.exec();
// Persist naming choice (if not disabled)
if (clickedButton != disableBtn) {
bool useOfficial = namingCombo->currentIndex() == 1;
SettingsCache::instance().cardsDisplay().setDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames(
useOfficial);
}
connect(&SettingsCache::instance().cardsDisplay(),
&CardsDisplaySettings::deckEditorCommanderSpellbookIntegrationEnabledChanged, this,
&DeckEditorDeckDockWidget::maybeAutoEstimateBracket);
connect(&SettingsCache::instance().cardsDisplay(),
&CardsDisplaySettings::deckEditorCommanderSpellbookIntegrationUseOfficialBracketNamesChanged, this,
&DeckEditorDeckDockWidget::maybeAutoEstimateBracket);
// Persist integration mode
if (clickedButton == disableBtn) {
SettingsCache::instance().cardsDisplay().setDeckEditorCommanderSpellbookIntegrationEnabled(
deckEditorCommanderSpellbookIntegrationEnabledIndexDisabled);
return false;
}
if (clickedButton == enableBtn) {
SettingsCache::instance().cardsDisplay().setDeckEditorCommanderSpellbookIntegrationEnabled(
deckEditorCommanderSpellbookIntegrationEnabledIndexEnabled);
return true;
}
if (clickedButton == automaticBtn) {
SettingsCache::instance().cardsDisplay().setDeckEditorCommanderSpellbookIntegrationEnabled(
deckEditorCommanderSpellbookIntegrationEnabledIndexAutomatic);
return true;
}
return false;
}
void DeckEditorDeckDockWidget::updateBracketVisibility(bool visible)
{
bracketLabel->setVisible(visible);
bracketValueLabel->setVisible(visible);
bracketInfoButton->setVisible(visible);
bracketRefreshButton->setVisible(visible);
}
void DeckEditorDeckDockWidget::requestBracketEstimate()
{
bracketRefreshButton->setEnabled(false);
bracketInfoButton->setEnabled(false);
bracketValueLabel->setText(tr("Calculating…"));
requestId = CommanderBracketService::instance().estimateBracket(deckStateManager->getDeckList(), this);
}
void DeckEditorDeckDockWidget::onEstimateBracketFinished(quint64 id,
QObject *requester,
const CommanderBracketEstimate &result)
{
if (requester != this || id != requestId) {
return;
}
BracketExplainer explainer;
lastBracketExplanation = explainer.explain(result.rawResult);
// Display bracket
bracketValueLabel->setText(
SettingsCache::instance().cardsDisplay().getDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames()
? result.officialName
: result.displayName);
bracketRefreshButton->setEnabled(true);
// Build tooltip
QString tooltip;
for (const auto &section : lastBracketExplanation.sections) {
tooltip += "<b>" + section.title + "</b><br>";
for (const auto &line : section.bulletPoints) {
tooltip += "• " + line + "<br>";
}
tooltip += "<br>";
}
bracketInfoButton->setToolTip(tooltip);
bracketInfoButton->setEnabled(!tooltip.isEmpty());
}
void DeckEditorDeckDockWidget::initializeFormats()
{
QStringList allFormats = CardDatabaseManager::query()->getAllFormatsWithCount().keys();
@ -509,61 +318,10 @@ void DeckEditorDeckDockWidget::initializeFormats()
deckStateManager->setFormat(""); // clear format if deselected
}
const bool isCommander = (formatKey.compare("commander", Qt::CaseInsensitive) == 0);
const bool commanderSpellbookIntegrationEnabled =
SettingsCache::instance().cardsDisplay().getDeckEditorCommanderSpellbookIntegrationEnabled() !=
deckEditorCommanderSpellbookIntegrationEnabledIndexDisabled;
const bool bracketVisible = isCommander && commanderSpellbookIntegrationEnabled;
updateBracketVisibility(bracketVisible);
if (!isCommander) {
bracketValueLabel->setText("-");
bracketInfoButton->setToolTip({});
bracketInfoButton->setEnabled(false);
bracketRefreshButton->setEnabled(false);
} else {
bracketRefreshButton->setEnabled(true);
maybeAutoEstimateBracket();
}
commanderBracketWidget->setDeck(deckStateManager->getDeckListShared());
});
maybeAutoEstimateBracket();
}
void DeckEditorDeckDockWidget::maybeAutoEstimateBracket()
{
const QString formatKey = deckStateManager->getDeckList().getGameFormat();
const bool isCommander = (formatKey.compare("commander", Qt::CaseInsensitive) == 0);
int mode = SettingsCache::instance().cardsDisplay().getDeckEditorCommanderSpellbookIntegrationEnabled();
if (!isCommander || mode == deckEditorCommanderSpellbookIntegrationEnabledIndexDisabled) {
updateBracketVisibility(false);
return;
}
if (mode == deckEditorCommanderSpellbookIntegrationEnabledIndexUnprompted) {
if (!promptCommanderSpellbookIntegration()) {
updateBracketVisibility(false);
return;
}
}
updateBracketVisibility(true);
mode = SettingsCache::instance().cardsDisplay().getDeckEditorCommanderSpellbookIntegrationEnabled();
if (mode != deckEditorCommanderSpellbookIntegrationEnabledIndexAutomatic) {
return;
}
// Avoid firing if we already have a result or a request in flight
if (!bracketRefreshButton->isEnabled()) {
return;
}
// Defer to avoid races during init / model rebuild
QTimer::singleShot(0, this, &DeckEditorDeckDockWidget::requestBracketEstimate);
commanderBracketWidget->setDeck(deckStateManager->getDeckListShared());
}
ExactCard DeckEditorDeckDockWidget::getCurrentCard()
@ -745,6 +503,8 @@ void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel()
formatComboBox->setCurrentIndex(formatComboBox->findData(deckStateManager->getMetadata().gameFormat));
formatComboBox->blockSignals(false);
commanderBracketWidget->setDeck(deckStateManager->getDeckListShared());
deckTagsDisplayWidget->blockSignals(true);
deckTagsDisplayWidget->setTags(deckStateManager->getMetadata().tags);
deckTagsDisplayWidget->blockSignals(false);
@ -998,8 +758,7 @@ void DeckEditorDeckDockWidget::retranslateUi()
commentsLabel->setText(tr("&Comments:"));
activeGroupCriteriaLabel->setText(tr("Group by:"));
formatLabel->setText(tr("Format:"));
bracketInfoButton->setToolTip(tr("Why this bracket?"));
bracketRefreshButton->setToolTip(tr("Recalculate bracket"));
commanderBracketWidget->retranslateUi();
hashLabel1->setText(tr("Hash:"));
@ -1007,4 +766,4 @@ void DeckEditorDeckDockWidget::retranslateUi()
aDecrement->setText(tr("&Decrement number"));
aRemoveCard->setText(tr("&Remove row"));
aSwapCard->setText(tr("Swap card to/from sideboard"));
}
}

View file

@ -10,7 +10,6 @@
#include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h"
#include "../../key_signals.h"
#include "../tabs/api/commander_spellbook/commander_spellbook_bracket_explainer.h"
#include "../utility/custom_line_edit.h"
#include "../visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h"
#include "deck_list_history_manager_widget.h"
@ -22,7 +21,7 @@
#include <QTreeView>
#include <libcockatrice/card/card_info.h>
struct CommanderBracketEstimate;
class CommanderBracketWidget;
class DeckListModel;
class AbstractTabDeckEditor;
class DeckEditorDeckDockWidget : public QDockWidget
@ -35,9 +34,6 @@ public:
QTreeView *deckView;
QComboBox *bannerCardComboBox;
void createDeckDock();
bool promptCommanderSpellbookIntegration();
void updateBracketVisibility(bool visible);
void requestBracketEstimate();
ExactCard getCurrentCard();
void retranslateUi();
@ -64,8 +60,6 @@ public slots:
void actSwapSelection();
void actRemoveCard();
void initializeFormats();
void maybeAutoEstimateBracket();
void onEstimateBracketFinished(quint64 id, QObject *requester, const CommanderBracketEstimate &result);
signals:
void selectedCardChanged(const ExactCard &card);
@ -96,14 +90,7 @@ private:
QAction *aRemoveCard, *aIncrement, *aDecrement, *aSwapCard;
QLabel *bracketLabel;
QLabel *bracketValueLabel;
QToolButton *bracketInfoButton;
QToolButton *bracketRefreshButton;
BracketExplanation lastBracketExplanation;
quint64 requestId;
CommanderBracketWidget *commanderBracketWidget;
DeckListModel *getModel() const;
[[nodiscard]] QModelIndexList getSelectedCardNodeSourceIndices() const;

View file

@ -57,6 +57,14 @@ public:
*/
const DeckList &getDeckList() const;
/**
* @brief Gets the underlying DeckList.
*/
QSharedPointer<DeckList> getDeckListShared() const
{
return deckList;
}
/**
* @brief Creates a LoadedDeck containing the contents of the current deck and the current LoadInfo.
*

View file

@ -2,6 +2,7 @@
#include "../../../client/settings/cache_settings.h"
#include "../interface/widgets/tabs/tab_supervisor.h"
#include "../tabs/api/commander_spellbook/commander_spellbook_bracket_explainer.h"
#include <QGridLayout>
#include <libcockatrice/settings/cards_display_settings.h>
@ -161,56 +162,53 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage()
connect(&defaultDeckEditorTypeSelector, QOverload<int>::of(&QComboBox::currentIndexChanged),
&SettingsCache::instance().deckEditor(), &DeckEditorSettings::setDefaultDeckEditorType);
deckEditorCommanderSpellBookIntegrationUseOfficialBracketNamesExplainer.setText("?");
deckEditorCommanderSpellBookIntegrationUseOfficialBracketNamesExplainer.setAutoRaise(true);
deckEditorCommanderSpellBookIntegrationUseOfficialBracketNamesExplainer.setEnabled(false);
commanderSpellbookIntegrationUseOfficialBracketNamesExplainer.setText("?");
commanderSpellbookIntegrationUseOfficialBracketNamesExplainer.setAutoRaise(true);
commanderSpellbookIntegrationUseOfficialBracketNamesExplainer.setEnabled(false);
// Add items with userData = internal enum
deckEditorCommanderSpellbookIntegrationEnabledSelector.addItem(
tr("Disabled"), deckEditorCommanderSpellbookIntegrationEnabledIndexDisabled);
deckEditorCommanderSpellbookIntegrationEnabledSelector.addItem(
tr("Enabled"), deckEditorCommanderSpellbookIntegrationEnabledIndexEnabled);
deckEditorCommanderSpellbookIntegrationEnabledSelector.addItem(
tr("Automatic"), deckEditorCommanderSpellbookIntegrationEnabledIndexAutomatic);
commanderSpellbookIntegrationEnabledSelector.addItem(tr("Disabled"),
commanderSpellbookIntegrationEnabledIndexDisabled);
commanderSpellbookIntegrationEnabledSelector.addItem(tr("Enabled"),
commanderSpellbookIntegrationEnabledIndexEnabled);
commanderSpellbookIntegrationEnabledSelector.addItem(tr("Automatic"),
commanderSpellbookIntegrationEnabledIndexAutomatic);
int storedMode = SettingsCache::instance().cardsDisplay().getDeckEditorCommanderSpellbookIntegrationEnabled();
for (int i = 0; i < deckEditorCommanderSpellbookIntegrationEnabledSelector.count(); ++i) {
if (deckEditorCommanderSpellbookIntegrationEnabledSelector.itemData(i).toInt() == storedMode) {
deckEditorCommanderSpellbookIntegrationEnabledSelector.setCurrentIndex(i);
int storedMode = SettingsCache::instance().deckEditor().getCommanderSpellbookIntegrationEnabled();
for (int i = 0; i < commanderSpellbookIntegrationEnabledSelector.count(); ++i) {
if (commanderSpellbookIntegrationEnabledSelector.itemData(i).toInt() == storedMode) {
commanderSpellbookIntegrationEnabledSelector.setCurrentIndex(i);
break;
}
}
connect(&deckEditorCommanderSpellbookIntegrationEnabledSelector,
QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) {
int mode = deckEditorCommanderSpellbookIntegrationEnabledSelector.itemData(index).toInt();
SettingsCache::instance().cardsDisplay().setDeckEditorCommanderSpellbookIntegrationEnabled(mode);
connect(&commanderSpellbookIntegrationEnabledSelector, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
[this](int index) {
int mode = commanderSpellbookIntegrationEnabledSelector.itemData(index).toInt();
SettingsCache::instance().deckEditor().setCommanderSpellbookIntegrationEnabled(mode);
updateCommanderSpellbookUiState();
});
deckEditorCommanderSpellbookIntegrationBracketNamingSelector.addItem(
commanderSpellbookIntegrationBracketNamingSelector.addItem(
tr("CommanderSpellbook bracket names")); // index 0 = false
deckEditorCommanderSpellbookIntegrationBracketNamingSelector.addItem(
commanderSpellbookIntegrationBracketNamingSelector.addItem(
tr("Official Commander bracket names (approximate)")); // index 1 = true
deckEditorCommanderSpellbookIntegrationBracketNamingSelector.setCurrentIndex(
SettingsCache::instance().cardsDisplay().getDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames()
? 1
: 0);
commanderSpellbookIntegrationBracketNamingSelector.setCurrentIndex(
SettingsCache::instance().deckEditor().getCommanderSpellbookIntegrationUseOfficialBracketNames() ? 1 : 0);
connect(
&deckEditorCommanderSpellbookIntegrationBracketNamingSelector,
QOverload<int>::of(&QComboBox::currentIndexChanged), &SettingsCache::instance(), [](int index) {
SettingsCache::instance().cardsDisplay().setDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames(
index == 1);
});
connect(&commanderSpellbookIntegrationBracketNamingSelector, QOverload<int>::of(&QComboBox::currentIndexChanged),
&SettingsCache::instance(), [](int index) {
SettingsCache::instance().deckEditor().setCommanderSpellbookIntegrationUseOfficialBracketNames(index ==
1);
});
updateCommanderSpellbookUiState();
auto *labelLayout = new QHBoxLayout;
labelLayout->setContentsMargins(0, 0, 0, 0);
labelLayout->addWidget(&deckEditorCommanderSpellbookIntegrationUseOfficialBracketNamesLabel);
labelLayout->addWidget(&deckEditorCommanderSpellBookIntegrationUseOfficialBracketNamesExplainer);
labelLayout->addWidget(&commanderSpellbookIntegrationUseOfficialBracketNamesLabel);
labelLayout->addWidget(&commanderSpellbookIntegrationUseOfficialBracketNamesExplainer);
auto *labelWidget = new QWidget;
labelWidget->setLayout(labelLayout);
@ -223,10 +221,10 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage()
deckEditorGrid->addWidget(&visualDeckStoragePromptForConversionSelector, 3, 1);
deckEditorGrid->addWidget(&defaultDeckEditorTypeLabel, 4, 0);
deckEditorGrid->addWidget(&defaultDeckEditorTypeSelector, 4, 1);
deckEditorGrid->addWidget(&deckEditorCommanderSpellbookIntegrationEnabledLabel, 5, 0);
deckEditorGrid->addWidget(&deckEditorCommanderSpellbookIntegrationEnabledSelector, 5, 1);
deckEditorGrid->addWidget(&commanderSpellbookIntegrationEnabledLabel, 5, 0);
deckEditorGrid->addWidget(&commanderSpellbookIntegrationEnabledSelector, 5, 1);
deckEditorGrid->addWidget(labelWidget, 6, 0);
deckEditorGrid->addWidget(&deckEditorCommanderSpellbookIntegrationBracketNamingSelector);
deckEditorGrid->addWidget(&commanderSpellbookIntegrationBracketNamingSelector, 6, 1);
deckEditorGroupBox = new QGroupBox;
deckEditorGroupBox->setLayout(deckEditorGrid);
@ -272,23 +270,22 @@ void UserInterfaceSettingsPage::setNotificationEnabled(QT_STATE_CHANGED_T i)
void UserInterfaceSettingsPage::updateCommanderSpellbookUiState()
{
const int mode = SettingsCache::instance().cardsDisplay().getDeckEditorCommanderSpellbookIntegrationEnabled();
const int mode = SettingsCache::instance().deckEditor().getCommanderSpellbookIntegrationEnabled();
const bool enabled = mode != deckEditorCommanderSpellbookIntegrationEnabledIndexDisabled &&
mode != deckEditorCommanderSpellbookIntegrationEnabledIndexUnprompted;
const bool enabled = mode != commanderSpellbookIntegrationEnabledIndexDisabled &&
mode != commanderSpellbookIntegrationEnabledIndexUnprompted;
deckEditorCommanderSpellbookIntegrationBracketNamingSelector.setEnabled(enabled);
deckEditorCommanderSpellBookIntegrationUseOfficialBracketNamesExplainer.setEnabled(enabled);
deckEditorCommanderSpellbookIntegrationUseOfficialBracketNamesLabel.setVisible(enabled);
deckEditorCommanderSpellBookIntegrationUseOfficialBracketNamesExplainer.setVisible(enabled);
deckEditorCommanderSpellbookIntegrationBracketNamingSelector.setVisible(enabled);
commanderSpellbookIntegrationBracketNamingSelector.setEnabled(enabled);
commanderSpellbookIntegrationUseOfficialBracketNamesExplainer.setEnabled(enabled);
commanderSpellbookIntegrationUseOfficialBracketNamesLabel.setVisible(enabled);
commanderSpellbookIntegrationUseOfficialBracketNamesExplainer.setVisible(enabled);
commanderSpellbookIntegrationBracketNamingSelector.setVisible(enabled);
if (enabled) {
// Sync selector with the current stored bool
const bool useOfficial = SettingsCache::instance()
.cardsDisplay()
.getDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames();
deckEditorCommanderSpellbookIntegrationBracketNamingSelector.setCurrentIndex(useOfficial ? 1 : 0);
const bool useOfficial =
SettingsCache::instance().deckEditor().getCommanderSpellbookIntegrationUseOfficialBracketNames();
commanderSpellbookIntegrationBracketNamingSelector.setCurrentIndex(useOfficial ? 1 : 0);
}
}
@ -330,22 +327,21 @@ void UserInterfaceSettingsPage::retranslateUi()
defaultDeckEditorTypeSelector.setItemText(TabSupervisor::ClassicDeckEditor, tr("Classic Deck Editor"));
defaultDeckEditorTypeSelector.setItemText(TabSupervisor::VisualDeckEditor, tr("Visual Deck Editor"));
deckEditorCommanderSpellbookIntegrationEnabledLabel.setText(
commanderSpellbookIntegrationEnabledLabel.setText(
tr("CommanderSpellbook integration to estimate commander bracket"));
deckEditorCommanderSpellbookIntegrationEnabledSelector.setItemText(
deckEditorCommanderSpellbookIntegrationEnabledIndexDisabled, tr("Disabled"));
deckEditorCommanderSpellbookIntegrationEnabledSelector.setItemText(
deckEditorCommanderSpellbookIntegrationEnabledIndexEnabled, tr("Enabled"));
deckEditorCommanderSpellbookIntegrationEnabledSelector.setItemText(
deckEditorCommanderSpellbookIntegrationEnabledIndexAutomatic, tr("Automatic"));
deckEditorCommanderSpellbookIntegrationUseOfficialBracketNamesLabel.setText(tr("Bracket naming"));
deckEditorCommanderSpellbookIntegrationBracketNamingSelector.setItemText(
commanderSpellbookIntegrationEnabledSelector.setItemText(commanderSpellbookIntegrationEnabledIndexDisabled,
tr("Disabled"));
commanderSpellbookIntegrationEnabledSelector.setItemText(commanderSpellbookIntegrationEnabledIndexEnabled,
tr("Enabled"));
commanderSpellbookIntegrationEnabledSelector.setItemText(commanderSpellbookIntegrationEnabledIndexAutomatic,
tr("Automatic"));
commanderSpellbookIntegrationUseOfficialBracketNamesLabel.setText(tr("Bracket naming"));
commanderSpellbookIntegrationBracketNamingSelector.setItemText(
0, CommanderBracketNames::CommanderSpellbookBracketNames);
deckEditorCommanderSpellbookIntegrationBracketNamingSelector.setItemText(
commanderSpellbookIntegrationBracketNamingSelector.setItemText(
1, CommanderBracketNames::OfficialCommanderBracketNames);
deckEditorCommanderSpellBookIntegrationUseOfficialBracketNamesExplainer.setToolTip(
CommanderBracketNames::Explainer);
commanderSpellbookIntegrationUseOfficialBracketNamesExplainer.setToolTip(CommanderBracketNames::Explainer);
replayGroupBox->setTitle(tr("Replay settings"));
rewindBufferingMsLabel.setText(tr("Buffer time for backwards skip via shortcut:"));
rewindBufferingMsBox.setSuffix(" ms");

View file

@ -12,21 +12,6 @@
#include <libcockatrice/settings/cards_display_settings.h>
#include <libcockatrice/utility/macros.h>
namespace CommanderBracketNames
{
inline const char *CommanderSpellbookBracketNames = QT_TR_NOOP("CommanderSpellbook");
inline const char *OfficialCommanderBracketNames = QT_TR_NOOP("Official (approximate)");
inline const char *Explainer = QT_TR_NOOP(
"The bracket system combines both objective data, as well as subjective play experience to estimate a "
"bracket for a deck.\nCommanderSpellbook's estimation is algorithmical, which means that it can only operate "
"on the objective data, not the subjective intent. \nThey have chosen to represent this by defining their "
"own bracket system which matches their algorithm.\n"
"This custom bracket system maps loosely to the standard system. \nYou may choose to use these mapped "
"standardized names if these are more familiar to you, however, you should keep in mind that these are just "
"rough estimations.\n\nAlways consider the subjective factors of the bracket system when determing a deck's "
"final bracket!");
} // namespace CommanderBracketNames
class UserInterfaceSettingsPage : public AbstractSettingsPage
{
Q_OBJECT
@ -57,11 +42,11 @@ private:
QCheckBox visualDeckStorageSelectionAnimationCheckBox;
QLabel defaultDeckEditorTypeLabel;
QComboBox defaultDeckEditorTypeSelector;
QLabel deckEditorCommanderSpellbookIntegrationEnabledLabel;
QComboBox deckEditorCommanderSpellbookIntegrationEnabledSelector;
QLabel deckEditorCommanderSpellbookIntegrationUseOfficialBracketNamesLabel;
QToolButton deckEditorCommanderSpellBookIntegrationUseOfficialBracketNamesExplainer;
QComboBox deckEditorCommanderSpellbookIntegrationBracketNamingSelector;
QLabel commanderSpellbookIntegrationEnabledLabel;
QComboBox commanderSpellbookIntegrationEnabledSelector;
QLabel commanderSpellbookIntegrationUseOfficialBracketNamesLabel;
QToolButton commanderSpellbookIntegrationUseOfficialBracketNamesExplainer;
QComboBox commanderSpellbookIntegrationBracketNamingSelector;
QLabel rewindBufferingMsLabel;
QSpinBox rewindBufferingMsBox;
QGroupBox *generalGroupBox;

View file

@ -1,9 +1,11 @@
#include "card_in_deck_request.h"
void CardInDeckRequest::fromJson(const QJsonObject &json)
CardInDeckRequest CardInDeckRequest::fromJson(const QJsonObject &json)
{
card = json.value("card").toString();
quantity = json.value("quantity").toInt();
CardInDeckRequest request;
request.card = json.value("card").toString();
request.quantity = json.value("quantity").toInt();
return request;
}
QJsonObject CardInDeckRequest::toJson() const
@ -13,9 +15,3 @@ QJsonObject CardInDeckRequest::toJson() const
json.insert("quantity", quantity);
return json;
}
void CardInDeckRequest::debugPrint() const
{
qDebug() << "Card:" << card;
qDebug() << "Quantity:" << quantity;
}

View file

@ -2,20 +2,11 @@
#define COCKATRICE_CARD_IN_DECK_REQUEST_H
#include <QJsonObject>
class CardInDeckRequest
struct CardInDeckRequest
{
public:
// Constructor
CardInDeckRequest() = default;
// Parse deck-related data from JSON
void fromJson(const QJsonObject &json);
static CardInDeckRequest fromJson(const QJsonObject &json);
QJsonObject toJson() const;
// Debug method for logging
void debugPrint() const;
private:
QString card;
int quantity;
};

View file

@ -1,22 +1,26 @@
#include "commander_spellbook_card_result.h"
void CommanderSpellbookCardResult::fromJson(const QJsonObject &json)
CommanderSpellbookCardResult CommanderSpellbookCardResult::fromJson(const QJsonObject &json)
{
id = json.value("id").toString();
name = json.value("name").toString();
oracleId = json.value("oracleId").toString();
spoiler = json.value("spoiler").toBool();
typeLine = json.value("typeLine").toString();
CommanderSpellbookCardResult result;
imageUriFrontPng = json.value("imageUriFrontPng").toString();
imageUriFrontLarge = json.value("imageUriFrontLarge").toString();
imageUriFrontNormal = json.value("imageUriFrontNormal").toString();
imageUriFrontSmall = json.value("imageUriFrontSmall").toString();
imageUriFrontArtCrop = json.value("imageUriFrontArtCrop").toString();
result.id = json.value("id").toString();
result.name = json.value("name").toString();
result.oracleId = json.value("oracleId").toString();
result.spoiler = json.value("spoiler").toBool();
result.typeLine = json.value("typeLine").toString();
imageUriBackPng = json.value("imageUriBackPng").toString();
imageUriBackLarge = json.value("imageUriBackLarge").toString();
imageUriBackNormal = json.value("imageUriBackNormal").toString();
imageUriBackSmall = json.value("imageUriBackSmall").toString();
imageUriBackArtCrop = json.value("imageUriBackArtCrop").toString();
result.imageUriFrontPng = json.value("imageUriFrontPng").toString();
result.imageUriFrontLarge = json.value("imageUriFrontLarge").toString();
result.imageUriFrontNormal = json.value("imageUriFrontNormal").toString();
result.imageUriFrontSmall = json.value("imageUriFrontSmall").toString();
result.imageUriFrontArtCrop = json.value("imageUriFrontArtCrop").toString();
result.imageUriBackPng = json.value("imageUriBackPng").toString();
result.imageUriBackLarge = json.value("imageUriBackLarge").toString();
result.imageUriBackNormal = json.value("imageUriBackNormal").toString();
result.imageUriBackSmall = json.value("imageUriBackSmall").toString();
result.imageUriBackArtCrop = json.value("imageUriBackArtCrop").toString();
return result;
}

View file

@ -3,10 +3,9 @@
#include <QJsonObject>
#include <QString>
class CommanderSpellbookCardResult
struct CommanderSpellbookCardResult
{
public:
void fromJson(const QJsonObject &json);
static CommanderSpellbookCardResult fromJson(const QJsonObject &json);
QString id;
QString name;

View file

@ -1,14 +1,11 @@
#include "commander_spellbook_deck_request.h"
#include <QDebug>
#include <QJsonArray>
#include <libcockatrice/deck_list/deck_list.h>
#include <libcockatrice/deck_list/tree/deck_list_card_node.h>
void CommanderSpellbookDeckRequest::fromJson(const QJsonObject &json)
CommanderSpellbookDeckRequest CommanderSpellbookDeckRequest::fromJson(const QJsonObject &json)
{
mainDeck.clear();
commanderDeck.clear();
CommanderSpellbookDeckRequest request;
// Main deck
const QJsonArray mainArray = json.value("main").toArray();
@ -17,12 +14,10 @@ void CommanderSpellbookDeckRequest::fromJson(const QJsonObject &json)
continue;
}
CardInDeckRequest card;
card.fromJson(value.toObject());
mainDeck.append(card);
request.mainDeck.append(CardInDeckRequest::fromJson(value.toObject()));
// Max size allowed by commanderspellbook
if (mainDeck.size() >= 600) {
if (request.mainDeck.size() >= 600) {
break;
}
}
@ -34,15 +29,15 @@ void CommanderSpellbookDeckRequest::fromJson(const QJsonObject &json)
continue;
}
CardInDeckRequest card;
card.fromJson(value.toObject());
commanderDeck.append(card);
request.commanderDeck.append(CardInDeckRequest::fromJson(value.toObject()));
// Max size allowed by commanderspellbook
if (commanderDeck.size() >= 12) {
if (request.commanderDeck.size() >= 12) {
break;
}
}
return request;
}
QJsonObject CommanderSpellbookDeckRequest::toJson() const
@ -65,10 +60,9 @@ QJsonObject CommanderSpellbookDeckRequest::toJson() const
return json;
}
void CommanderSpellbookDeckRequest::fromDeckList(const DeckList &deck)
CommanderSpellbookDeckRequest CommanderSpellbookDeckRequest::fromDeckList(const DeckList &deck)
{
mainDeck.clear();
commanderDeck.clear();
CommanderSpellbookDeckRequest request;
// --- Mainboard ---
const auto mainCards = deck.getCardNodes({DECK_ZONE_MAIN});
@ -77,16 +71,13 @@ void CommanderSpellbookDeckRequest::fromDeckList(const DeckList &deck)
continue;
}
CardInDeckRequest req;
QJsonObject json;
json.insert("card", node->getName());
json.insert("quantity", node->getNumber());
req.fromJson(json);
mainDeck.append(req);
request.mainDeck.append(CardInDeckRequest::fromJson(json));
// Max size allowed by commanderspellbook
if (mainDeck.size() >= 600) {
if (request.mainDeck.size() >= 600) {
break;
}
}
@ -94,25 +85,11 @@ void CommanderSpellbookDeckRequest::fromDeckList(const DeckList &deck)
// --- Commander (bannerCard) ---
const auto &metadata = deck.getMetadata();
if (!metadata.bannerCard.name.isEmpty()) {
CardInDeckRequest commander;
QJsonObject json;
json.insert("card", metadata.bannerCard.name);
json.insert("quantity", 1);
commander.fromJson(json);
commanderDeck.append(commander);
}
}
void CommanderSpellbookDeckRequest::debugPrint() const
{
qDebug() << "Main deck:";
for (const CardInDeckRequest &card : mainDeck) {
card.debugPrint();
}
qDebug() << "Commanders:";
for (const CardInDeckRequest &card : commanderDeck) {
card.debugPrint();
request.commanderDeck.append(CardInDeckRequest::fromJson(json));
}
return request;
}

View file

@ -4,31 +4,16 @@
#include "libcockatrice/deck_list/deck_list.h"
#include <QJsonObject>
#include <QVector>
#include <QList>
class CommanderSpellbookDeckRequest
struct CommanderSpellbookDeckRequest
{
public:
CommanderSpellbookDeckRequest() = default;
void fromJson(const QJsonObject &json);
static CommanderSpellbookDeckRequest fromJson(const QJsonObject &json);
static CommanderSpellbookDeckRequest fromDeckList(const DeckList &deck);
QJsonObject toJson() const;
void fromDeckList(const DeckList &deck);
void debugPrint() const;
const QVector<CardInDeckRequest> &main() const
{
return mainDeck;
}
const QVector<CardInDeckRequest> &commanders() const
{
return commanderDeck;
}
private:
QVector<CardInDeckRequest> mainDeck; // maxItems: 600
QVector<CardInDeckRequest> commanderDeck; // maxItems: 12
QList<CardInDeckRequest> mainDeck; // maxItems: 600
QList<CardInDeckRequest> commanderDeck; // maxItems: 12
};
#endif // COCKATRICE_COMMANDER_SPELLBOOK_DECK_REQUEST_H

View file

@ -1,23 +1,10 @@
#include "commander_spellbook_estimate_bracket_result.h"
void EstimateBracketResult::fromJson(const QJsonObject &json)
EstimateBracketResult EstimateBracketResult::fromJson(const QJsonObject &json)
{
bracketTag = json.value("bracketTag").toString();
EstimateBracketResult result;
gameChangerCards.clear();
massLandDenialCards.clear();
extraTurnCards.clear();
massLandDenialTemplates.clear();
extraTurnTemplates.clear();
massLandDenialCombos.clear();
extraTurnCombos.clear();
lockCombos.clear();
skipTurnsCombos.clear();
definitelyTwoCardCombos.clear();
arguablyTwoCardCombos.clear();
result.bracketTag = json.value("bracketTag").toString();
//
// Cards
@ -29,19 +16,18 @@ void EstimateBracketResult::fromJson(const QJsonObject &json)
const QJsonObject obj = value.toObject();
CommanderSpellbookCardResult card;
card.fromJson(obj.value("card").toObject());
CommanderSpellbookCardResult card = CommanderSpellbookCardResult::fromJson(obj.value("card").toObject());
if (obj.value("gameChanger").toBool()) {
gameChangerCards.append(card);
result.gameChangerCards.append(card);
}
if (obj.value("massLandDenial").toBool()) {
massLandDenialCards.append(card);
result.massLandDenialCards.append(card);
}
if (obj.value("extraTurn").toBool()) {
extraTurnCards.append(card);
result.extraTurnCards.append(card);
}
}
@ -55,15 +41,14 @@ void EstimateBracketResult::fromJson(const QJsonObject &json)
const QJsonObject obj = value.toObject();
CommanderSpellbookVariantResult variant;
variant.fromJson(obj);
CommanderSpellbookVariantResult variant = CommanderSpellbookVariantResult::fromJson(obj);
if (obj.value("massLandDenial").toBool()) {
massLandDenialTemplates.append(variant);
result.massLandDenialTemplates.append(variant);
}
if (obj.value("extraTurn").toBool()) {
extraTurnTemplates.append(variant);
result.extraTurnTemplates.append(variant);
}
}
@ -77,31 +62,32 @@ void EstimateBracketResult::fromJson(const QJsonObject &json)
const QJsonObject obj = value.toObject();
CommanderSpellbookVariantResult combo;
combo.fromJson(obj);
CommanderSpellbookVariantResult combo = CommanderSpellbookVariantResult::fromJson(obj);
if (obj.value("massLandDenial").toBool()) {
massLandDenialCombos.append(combo);
result.massLandDenialCombos.append(combo);
}
if (obj.value("extraTurn").toBool()) {
extraTurnCombos.append(combo);
result.extraTurnCombos.append(combo);
}
if (obj.value("lock").toBool()) {
lockCombos.append(combo);
result.lockCombos.append(combo);
}
if (obj.value("skipTurns").toBool()) {
skipTurnsCombos.append(combo);
result.skipTurnsCombos.append(combo);
}
if (obj.value("definitelyTwoCard").toBool()) {
definitelyTwoCardCombos.append(combo);
result.definitelyTwoCardCombos.append(combo);
}
if (obj.value("arguablyTwoCard").toBool()) {
arguablyTwoCardCombos.append(combo);
result.arguablyTwoCardCombos.append(combo);
}
}
}
return result;
}

View file

@ -4,29 +4,29 @@
#include "commander_spellbook_card_result.h"
#include "commander_spellbook_variant_result.h"
#include <QVector>
#include <QJsonObject>
#include <QList>
class EstimateBracketResult
struct EstimateBracketResult
{
public:
void fromJson(const QJsonObject &json);
static EstimateBracketResult fromJson(const QJsonObject &json);
QString bracketTag;
QVector<CommanderSpellbookCardResult> gameChangerCards;
QVector<CommanderSpellbookCardResult> massLandDenialCards;
QVector<CommanderSpellbookCardResult> extraTurnCards;
QList<CommanderSpellbookCardResult> gameChangerCards;
QList<CommanderSpellbookCardResult> massLandDenialCards;
QList<CommanderSpellbookCardResult> extraTurnCards;
QVector<CommanderSpellbookVariantResult> massLandDenialTemplates;
QVector<CommanderSpellbookVariantResult> extraTurnTemplates;
QList<CommanderSpellbookVariantResult> massLandDenialTemplates;
QList<CommanderSpellbookVariantResult> extraTurnTemplates;
QVector<CommanderSpellbookVariantResult> massLandDenialCombos;
QVector<CommanderSpellbookVariantResult> extraTurnCombos;
QVector<CommanderSpellbookVariantResult> lockCombos;
QVector<CommanderSpellbookVariantResult> skipTurnsCombos;
QList<CommanderSpellbookVariantResult> massLandDenialCombos;
QList<CommanderSpellbookVariantResult> extraTurnCombos;
QList<CommanderSpellbookVariantResult> lockCombos;
QList<CommanderSpellbookVariantResult> skipTurnsCombos;
QVector<CommanderSpellbookVariantResult> definitelyTwoCardCombos;
QVector<CommanderSpellbookVariantResult> arguablyTwoCardCombos;
QList<CommanderSpellbookVariantResult> definitelyTwoCardCombos;
QList<CommanderSpellbookVariantResult> arguablyTwoCardCombos;
};
#endif
#endif // COCKATRICE_COMMANDER_SPELLBOOK_ESTIMATE_BRACKET_RESULT_H

View file

@ -1,31 +1,35 @@
#include "commander_spellbook_variant_result.h"
void CommanderSpellbookVariantResult::fromJson(const QJsonObject &json)
CommanderSpellbookVariantResult CommanderSpellbookVariantResult::fromJson(const QJsonObject &json)
{
id = json.value("id").toString();
status = json.value("status").toString();
CommanderSpellbookVariantResult result;
uses = json.value("uses").toArray();
cardRequires = json.value("requires").toArray();
produces = json.value("produces").toArray();
of = json.value("of").toArray();
includes = json.value("includes").toArray();
result.id = json.value("id").toString();
result.status = json.value("status").toString();
manaNeeded = json.value("manaNeeded").toArray();
manaValueNeeded = json.value("manaValueNeeded").toArray();
result.uses = json.value("uses").toArray();
result.cardRequires = json.value("requires").toArray();
result.produces = json.value("produces").toArray();
result.of = json.value("of").toArray();
result.includes = json.value("includes").toArray();
easyPrerequisites = json.value("easyPrerequisites").toArray();
notablePrerequisites = json.value("notablePrerequisites").toArray();
result.manaNeeded = json.value("manaNeeded").toArray();
result.manaValueNeeded = json.value("manaValueNeeded").toArray();
description = json.value("description").toString();
notes = json.value("notes").toString();
popularity = json.value("popularity").toDouble();
result.easyPrerequisites = json.value("easyPrerequisites").toArray();
result.notablePrerequisites = json.value("notablePrerequisites").toArray();
spoiler = json.value("spoiler").toBool();
bracketTag = json.value("bracketTag").toString();
result.description = json.value("description").toString();
result.notes = json.value("notes").toString();
result.popularity = json.value("popularity").toDouble();
legalities = json.value("legalities").toObject();
prices = json.value("prices").toObject();
result.spoiler = json.value("spoiler").toBool();
result.bracketTag = json.value("bracketTag").toString();
variantCount = json.value("variantCount").toInt();
result.legalities = json.value("legalities").toObject();
result.prices = json.value("prices").toObject();
result.variantCount = json.value("variantCount").toInt();
return result;
}

View file

@ -4,10 +4,9 @@
#include <QJsonArray>
#include <QJsonObject>
class CommanderSpellbookVariantResult
struct CommanderSpellbookVariantResult
{
public:
void fromJson(const QJsonObject &json);
static CommanderSpellbookVariantResult fromJson(const QJsonObject &json);
QString id;
QString status;

View file

@ -50,4 +50,4 @@ void CommanderBracketService::onEstimateBracketError(CommanderSpellbookApiAccess
const QString &error)
{
emit estimateError(id, requester, error);
}
}

View file

@ -42,4 +42,4 @@ private:
explicit CommanderBracketService(QObject *parent = nullptr);
};
#endif
#endif

View file

@ -0,0 +1,269 @@
#include "commander_bracket_widget.h"
#include "../../../../../client/settings/cache_settings.h"
#include "commander_bracket_service.h"
#include <QComboBox>
#include <QDialog>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QTimer>
#include <QToolButton>
#include <QVBoxLayout>
#include <libcockatrice/settings/deck_editor_settings.h>
CommanderBracketWidget::CommanderBracketWidget(QWidget *parent) : QWidget(parent)
{
bracketLabel = new QLabel(tr("Bracket:"), this);
bracketValueLabel = new QLabel(this);
bracketValueLabel->setText("-");
bracketValueLabel->setObjectName("bracketValueLabel");
bracketInfoButton = new QToolButton(this);
bracketInfoButton->setText("?");
bracketInfoButton->setAutoRaise(true);
bracketInfoButton->setEnabled(false);
bracketRefreshButton = new QToolButton(this);
bracketRefreshButton->setIcon(QPixmap("theme:icons/reload"));
bracketRefreshButton->setAutoRaise(true);
connect(bracketRefreshButton, &QToolButton::clicked, this, &CommanderBracketWidget::requestBracketEstimate);
auto *layout = new QGridLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(bracketLabel, 0, 0);
auto *bracketRow = new QHBoxLayout;
bracketRow->addWidget(bracketValueLabel);
bracketRow->addWidget(bracketInfoButton);
bracketRow->addWidget(bracketRefreshButton);
bracketRow->addStretch();
layout->addLayout(bracketRow, 0, 1);
connect(&CommanderBracketService::instance(), &CommanderBracketService::estimateFinished, this,
&CommanderBracketWidget::onEstimateBracketFinished);
connect(&CommanderBracketService::instance(), &CommanderBracketService::estimateError, this,
&CommanderBracketWidget::onEstimateBracketError);
connect(&SettingsCache::instance().deckEditor(), &DeckEditorSettings::commanderSpellbookIntegrationEnabledChanged,
this, &CommanderBracketWidget::maybeAutoEstimateBracket);
connect(&SettingsCache::instance().deckEditor(),
&DeckEditorSettings::commanderSpellbookIntegrationUseOfficialBracketNamesChanged, this,
&CommanderBracketWidget::maybeAutoEstimateBracket);
setVisible(false);
}
void CommanderBracketWidget::setDeck(const QSharedPointer<DeckList> &_deck)
{
deck = _deck;
requestId = 0; // invalidate any in-flight estimate for the previous deck
// Reset the displayed bracket
bracketValueLabel->setText("-");
bracketInfoButton->setToolTip({});
bracketInfoButton->setEnabled(false);
bracketRefreshButton->setEnabled(true);
maybeAutoEstimateBracket();
}
bool CommanderBracketWidget::promptCommanderSpellbookIntegration()
{
QDialog dialog(this);
dialog.setWindowTitle(tr("CommanderSpellbook integration"));
auto *mainLayout = new QVBoxLayout(&dialog);
// Main text
auto *label = new QLabel(tr("CommanderSpellbook can analyze your deck and estimate its Commander bracket.\n\n"
"This sends your deck list to an external service.\n\n"
"CommanderSpellbook uses its own bracket naming system based on their own algorithm. "
"These names can be mapped to the official Commander brackets, but the mapping "
"is only an approximation."));
label->setWordWrap(true);
mainLayout->addWidget(label);
// Naming selector
auto *formLayout = new QFormLayout;
auto *namingCombo = new QComboBox(&dialog);
namingCombo->addItem(tr("CommanderSpellbook bracket names"));
namingCombo->addItem(tr("Official Commander bracket names (approximate)"));
namingCombo->setCurrentIndex(
SettingsCache::instance().deckEditor().getCommanderSpellbookIntegrationUseOfficialBracketNames() ? 1 : 0);
// Create label + explainer button
auto *labelWidget = new QWidget(&dialog);
auto *labelLayout = new QHBoxLayout(labelWidget);
labelLayout->setContentsMargins(0, 0, 0, 0);
auto *namingLabel = new QLabel(tr("Bracket naming:"), labelWidget);
auto *explainerButton = new QToolButton(labelWidget);
explainerButton->setText("?");
explainerButton->setAutoRaise(true);
explainerButton->setEnabled(false);
explainerButton->setToolTip(CommanderBracketNames::Explainer);
labelLayout->addWidget(namingLabel);
labelLayout->addWidget(explainerButton);
labelLayout->addStretch(); // push the button next to label, combo stays aligned
// Add row with the custom label widget
formLayout->addRow(labelWidget, namingCombo);
mainLayout->addLayout(formLayout);
// Buttons
auto *buttonBox = new QDialogButtonBox(&dialog);
auto *enableBtn = buttonBox->addButton(tr("Enable"), QDialogButtonBox::AcceptRole);
auto *automaticBtn = buttonBox->addButton(tr("Automatic"), QDialogButtonBox::ApplyRole);
auto *disableBtn = buttonBox->addButton(tr("Disable"), QDialogButtonBox::RejectRole);
mainLayout->addWidget(buttonBox);
// Track which button was clicked
QAbstractButton *clickedButton = nullptr;
QObject::connect(buttonBox, &QDialogButtonBox::clicked, &dialog, [&](QAbstractButton *btn) {
clickedButton = btn;
dialog.accept();
});
dialog.exec();
// Persist naming choice (if not disabled)
if (clickedButton != disableBtn) {
bool useOfficial = namingCombo->currentIndex() == 1;
SettingsCache::instance().deckEditor().setCommanderSpellbookIntegrationUseOfficialBracketNames(useOfficial);
}
// Persist integration mode
if (clickedButton == disableBtn) {
SettingsCache::instance().deckEditor().setCommanderSpellbookIntegrationEnabled(
commanderSpellbookIntegrationEnabledIndexDisabled);
return false;
}
if (clickedButton == enableBtn) {
SettingsCache::instance().deckEditor().setCommanderSpellbookIntegrationEnabled(
commanderSpellbookIntegrationEnabledIndexEnabled);
return true;
}
if (clickedButton == automaticBtn) {
SettingsCache::instance().deckEditor().setCommanderSpellbookIntegrationEnabled(
commanderSpellbookIntegrationEnabledIndexAutomatic);
return true;
}
return false;
}
void CommanderBracketWidget::updateBracketVisibility(bool visible)
{
setVisible(visible);
}
void CommanderBracketWidget::requestBracketEstimate()
{
bracketRefreshButton->setEnabled(false);
bracketInfoButton->setEnabled(false);
bracketValueLabel->setText(tr("Calculating…"));
requestId = CommanderBracketService::instance().estimateBracket(*deck, this);
}
void CommanderBracketWidget::onEstimateBracketFinished(quint64 id,
QObject *requester,
const CommanderBracketEstimate &result)
{
if (requester != this || id != requestId) {
return;
}
BracketExplainer explainer;
lastBracketExplanation = explainer.explain(result.rawResult);
// Display bracket
bracketValueLabel->setText(
SettingsCache::instance().deckEditor().getCommanderSpellbookIntegrationUseOfficialBracketNames()
? result.officialName
: result.displayName);
bracketRefreshButton->setEnabled(true);
// Build tooltip
QString tooltip;
for (const auto &section : lastBracketExplanation.sections) {
tooltip += "<b>" + section.title + "</b><br>";
for (const auto &line : section.bulletPoints) {
tooltip += "• " + line + "<br>";
}
tooltip += "<br>";
}
bracketInfoButton->setToolTip(tooltip);
bracketInfoButton->setEnabled(!tooltip.isEmpty());
}
void CommanderBracketWidget::onEstimateBracketError(quint64 id, QObject *requester, const QString & /*error*/)
{
if (requester != this || id != requestId) {
return;
}
bracketValueLabel->setText("-");
bracketRefreshButton->setEnabled(true);
bracketInfoButton->setToolTip({});
bracketInfoButton->setEnabled(false);
}
void CommanderBracketWidget::maybeAutoEstimateBracket()
{
const QString formatKey = deck->getGameFormat();
const bool isCommander = (formatKey.compare("commander", Qt::CaseInsensitive) == 0);
int mode = SettingsCache::instance().deckEditor().getCommanderSpellbookIntegrationEnabled();
if (!isCommander || mode == commanderSpellbookIntegrationEnabledIndexDisabled) {
updateBracketVisibility(false);
return;
}
if (mode == commanderSpellbookIntegrationEnabledIndexUnprompted) {
if (prompting) {
return;
}
prompting = true;
const bool accepted = promptCommanderSpellbookIntegration();
prompting = false;
if (!accepted) {
updateBracketVisibility(false);
return;
}
}
updateBracketVisibility(true);
mode = SettingsCache::instance().deckEditor().getCommanderSpellbookIntegrationEnabled();
if (mode != commanderSpellbookIntegrationEnabledIndexAutomatic) {
return;
}
// Avoid firing if we already have a result or a request in flight
if (!bracketRefreshButton->isEnabled()) {
return;
}
// Defer to avoid races during init / model rebuild
QTimer::singleShot(0, this, &CommanderBracketWidget::requestBracketEstimate);
}
void CommanderBracketWidget::retranslateUi()
{
bracketLabel->setText(tr("Bracket:"));
bracketInfoButton->setToolTip(tr("Why this bracket?"));
bracketRefreshButton->setToolTip(tr("Recalculate bracket"));
}

View file

@ -0,0 +1,46 @@
#ifndef COCKATRICE_COMMANDER_BRACKET_WIDGET_H
#define COCKATRICE_COMMANDER_BRACKET_WIDGET_H
#include "commander_spellbook_bracket_explainer.h"
#include <QSharedPointer>
#include <QWidget>
#include <libcockatrice/deck_list/deck_list.h>
class QLabel;
class QToolButton;
struct CommanderBracketEstimate;
class CommanderBracketWidget : public QWidget
{
Q_OBJECT
public:
explicit CommanderBracketWidget(QWidget *parent = nullptr);
void setDeck(const QSharedPointer<DeckList> &_deck);
void retranslateUi();
private slots:
void requestBracketEstimate();
void onEstimateBracketFinished(quint64 id, QObject *requester, const CommanderBracketEstimate &result);
void onEstimateBracketError(quint64 id, QObject *requester, const QString &error);
void maybeAutoEstimateBracket();
private:
bool promptCommanderSpellbookIntegration();
void updateBracketVisibility(bool visible);
QSharedPointer<DeckList> deck;
bool prompting = false;
quint64 requestId = 0;
QLabel *bracketLabel;
QLabel *bracketValueLabel;
QToolButton *bracketInfoButton;
QToolButton *bracketRefreshButton;
BracketExplanation lastBracketExplanation;
};
#endif // COCKATRICE_COMMANDER_BRACKET_WIDGET_H

View file

@ -23,8 +23,7 @@ CommanderSpellbookApiAccessor::CommanderSpellbookApiAccessor(QObject *parent) :
CommanderSpellbookApiAccessor::RequestId CommanderSpellbookApiAccessor::estimateBracket(const DeckList &deck,
QObject *requester)
{
CommanderSpellbookDeckRequest deckRequest;
deckRequest.fromDeckList(deck);
CommanderSpellbookDeckRequest deckRequest = CommanderSpellbookDeckRequest::fromDeckList(deck);
QJsonDocument doc(deckRequest.toJson());
QByteArray body = doc.toJson(QJsonDocument::Compact);
@ -70,8 +69,7 @@ void CommanderSpellbookApiAccessor::onEstimateReplyFinished(QNetworkReply *reply
return;
}
EstimateBracketResult result;
result.fromJson(doc.object());
EstimateBracketResult result = EstimateBracketResult::fromJson(doc.object());
emit estimateBracketFinished(id, requester, result);
}

View file

@ -1,6 +1,6 @@
#include "commander_spellbook_bracket_explainer.h"
static QString cardList(const QVector<CommanderSpellbookCardResult> &cards, int max = 5)
static QString cardList(const QList<CommanderSpellbookCardResult> &cards, int max = 5)
{
QStringList names;
for (int i = 0; i < cards.size() && i < max; ++i) {
@ -14,7 +14,7 @@ static QString cardList(const QVector<CommanderSpellbookCardResult> &cards, int
return names.join(", ");
}
static QString comboCount(const QVector<CommanderSpellbookVariantResult> &variants)
static QString comboCount(const QList<CommanderSpellbookVariantResult> &variants)
{
return QString::number(variants.size());
}

View file

@ -2,6 +2,21 @@
#define COCKATRICE_COMMANDER_SPELLBOOK_BRACKET_EXPLAINER_H
#include "api_response/commander_spellbook_estimate_bracket_result.h"
namespace CommanderBracketNames
{
inline const char *CommanderSpellbookBracketNames = QT_TR_NOOP("CommanderSpellbook");
inline const char *OfficialCommanderBracketNames = QT_TR_NOOP("Official (approximate)");
inline const char *Explainer = QT_TR_NOOP(
"The bracket system combines both objective data, as well as subjective play experience to estimate a "
"bracket for a deck.\nCommanderSpellbook's estimation is algorithmical, which means that it can only operate "
"on the objective data, not the subjective intent. \nThey have chosen to represent this by defining their "
"own bracket system which matches their algorithm.\n"
"This custom bracket system maps loosely to the standard system. \nYou may choose to use these mapped "
"standardized names if these are more familiar to you, however, you should keep in mind that these are just "
"rough estimations.\n\nAlways consider the subjective factors of the bracket system when determing a deck's "
"final bracket!");
} // namespace CommanderBracketNames
struct BracketExplanationSection
{
QString title;

View file

@ -7,7 +7,7 @@
#include <QUrl>
#include <libcockatrice/settings/commander_bracket_settings.h>
#define COMMANDER_BRACKET_JSON "https://cockatrice.github.io/commander-brackets.json"
static const QUrl COMMANDER_BRACKET_JSON_URL(QStringLiteral("https://cockatrice.github.io/commander-brackets.json"));
HandleCommanderBrackets::HandleCommanderBrackets(QObject *parent)
: QObject(parent), nam(new QNetworkAccessManager(this)), reply(nullptr)
@ -16,7 +16,11 @@ HandleCommanderBrackets::HandleCommanderBrackets(QObject *parent)
void HandleCommanderBrackets::downloadBracketDefinitions()
{
reply = nam->get(QNetworkRequest(QUrl(COMMANDER_BRACKET_JSON)));
if (reply) {
return;
}
reply = nam->get(QNetworkRequest(COMMANDER_BRACKET_JSON_URL));
connect(reply, &QNetworkReply::finished, this, &HandleCommanderBrackets::actFinishParsingDownloadedData);
}
@ -55,4 +59,4 @@ void HandleCommanderBrackets::updateBracketDefinitions(const QVariantMap &jsonMa
const auto bracketList = jsonMap.value("brackets").toList();
SettingsCache::instance().commanderBrackets().saveDefinitions(bracketList);
SettingsCache::instance().commanderBrackets().reloadDefinitions(bracketList);
}
}

View file

@ -560,6 +560,8 @@ void MainWindow::startupConfigCheck()
actCheckClientUpdates();
}
actCheckCommanderBracketDefinitionUpdates();
if (SettingsCache::instance().network().getClientVersion() == CLIENT_INFO_NOT_SET) {
// no config found, 99% new clean install
qCInfo(WindowMainStartupVersionLog)

View file

@ -214,29 +214,3 @@ void CardsDisplaySettings::setSampleHandSize(int _sampleHandSize)
setValue(_sampleHandSize, "sampleHandSize", "cards", "cardSize");
emit sampleHandSizeChanged(_sampleHandSize);
}
int CardsDisplaySettings::getDeckEditorCommanderSpellbookIntegrationEnabled() const
{
return getValue("deckeditorcommanderspellbookintegrationenabled", "interface", QString(),
deckEditorCommanderSpellbookIntegrationEnabledIndexUnprompted)
.toInt();
}
bool CardsDisplaySettings::getDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames() const
{
return getValue("deckeditorcommanderspellbookintegrationuseofficialbracketnames", "interface", QString(), false)
.toBool();
}
void CardsDisplaySettings::setDeckEditorCommanderSpellbookIntegrationEnabled(int _enabled)
{
setValue(_enabled, "deckeditorcommanderspellbookintegrationenabled", "interface");
emit deckEditorCommanderSpellbookIntegrationEnabledChanged(_enabled);
}
void CardsDisplaySettings::setDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames(
bool _useOfficialBracketNames)
{
setValue(_useOfficialBracketNames, "deckeditorcommanderspellbookintegrationuseofficialbracketnames", "interface");
emit deckEditorCommanderSpellbookIntegrationUseOfficialBracketNamesChanged(_useOfficialBracketNames);
}

View file

@ -5,14 +5,6 @@
#include <libcockatrice/interfaces/interface_cards_display_settings_provider.h>
enum deckEditorCommanderSpellbookIntegrationEnabledIndex
{
deckEditorCommanderSpellbookIntegrationEnabledIndexDisabled,
deckEditorCommanderSpellbookIntegrationEnabledIndexEnabled,
deckEditorCommanderSpellbookIntegrationEnabledIndexAutomatic,
deckEditorCommanderSpellbookIntegrationEnabledIndexUnprompted,
};
class CardsDisplaySettings : public SettingsManager, public ICardsDisplaySettingsProvider
{
Q_OBJECT
@ -39,12 +31,6 @@ public:
[[nodiscard]] int getArchidektPreviewSize() const override;
[[nodiscard]] int getSampleHandSize() const override;
[[nodiscard]] int getDeckEditorCommanderSpellbookIntegrationEnabled() const;
[[nodiscard]] bool getDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames() const;
void setDeckEditorCommanderSpellbookIntegrationEnabled(int _deckEditorCommanderSpellbookIntegrationEnabled);
void setDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames(
bool _deckEditorCommanderSpellbookIntegrationUseOfficialBracketNames);
void setDisplayCardNames(bool _displayCardNames);
void setRoundCardCorners(bool _roundCardCorners);
void setOverrideAllCardArtWithPersonalPreference(bool _overrideAllCardArt);
@ -80,8 +66,6 @@ signals:
void edhRecCardSizeChanged();
void archidektPreviewSizeChanged();
void sampleHandSizeChanged(int amount);
void deckEditorCommanderSpellbookIntegrationEnabledChanged(int _enabled);
void deckEditorCommanderSpellbookIntegrationUseOfficialBracketNamesChanged(bool _useOfficialBracketNames);
public:
explicit CardsDisplaySettings(const QString &settingPath, QObject *parent = nullptr);

View file

@ -191,4 +191,4 @@ QString CommanderBracketSettings::explanation(const QString &tag) const
bool CommanderBracketSettings::contains(const QString &tag) const
{
return definitions.contains(tag);
}
}

View file

@ -46,3 +46,27 @@ void DeckEditorSettings::setDefaultDeckEditorType(int _defaultDeckEditorType)
{
setValue(_defaultDeckEditorType, "defaultDeckEditorType");
}
int DeckEditorSettings::getCommanderSpellbookIntegrationEnabled() const
{
return getValue("commanderspellbookintegrationenabled", QString(), QString(),
commanderSpellbookIntegrationEnabledIndexUnprompted)
.toInt();
}
bool DeckEditorSettings::getCommanderSpellbookIntegrationUseOfficialBracketNames() const
{
return getValue("commanderspellbookintegrationuseofficialbracketnames", QString(), QString(), false).toBool();
}
void DeckEditorSettings::setCommanderSpellbookIntegrationEnabled(int _commanderSpellbookIntegrationEnabled)
{
setValue(_commanderSpellbookIntegrationEnabled, "commanderspellbookintegrationenabled");
emit commanderSpellbookIntegrationEnabledChanged(_commanderSpellbookIntegrationEnabled);
}
void DeckEditorSettings::setCommanderSpellbookIntegrationUseOfficialBracketNames(bool _useOfficialBracketNames)
{
setValue(_useOfficialBracketNames, "commanderspellbookintegrationuseofficialbracketnames");
emit commanderSpellbookIntegrationUseOfficialBracketNamesChanged(_useOfficialBracketNames);
}

View file

@ -5,6 +5,14 @@
#include <libcockatrice/interfaces/interface_deck_editor_settings_provider.h>
enum commanderSpellbookIntegrationEnabledIndex
{
commanderSpellbookIntegrationEnabledIndexDisabled,
commanderSpellbookIntegrationEnabledIndexEnabled,
commanderSpellbookIntegrationEnabledIndexAutomatic,
commanderSpellbookIntegrationEnabledIndexUnprompted,
};
class DeckEditorSettings : public SettingsManager, public IDeckEditorSettingsProvider
{
Q_OBJECT
@ -15,15 +23,21 @@ public:
[[nodiscard]] bool getBannerCardComboBoxVisible() const override;
[[nodiscard]] bool getTagsWidgetVisible() const override;
[[nodiscard]] int getDefaultDeckEditorType() const override;
[[nodiscard]] int getCommanderSpellbookIntegrationEnabled() const;
[[nodiscard]] bool getCommanderSpellbookIntegrationUseOfficialBracketNames() const;
void setOpenDeckInNewTab(bool _openDeckInNewTab);
void setBannerCardComboBoxVisible(bool _bannerCardComboBoxVisible);
void setTagsWidgetVisible(bool _tagsWidgetVisible);
void setDefaultDeckEditorType(int _defaultDeckEditorType);
void setCommanderSpellbookIntegrationEnabled(int _commanderSpellbookIntegrationEnabled);
void setCommanderSpellbookIntegrationUseOfficialBracketNames(bool _useOfficialBracketNames);
signals:
void bannerCardComboBoxVisibleChanged(bool visible);
void tagsWidgetVisibleChanged(bool visible);
void commanderSpellbookIntegrationEnabledChanged(int enabled);
void commanderSpellbookIntegrationUseOfficialBracketNamesChanged(bool useOfficialBracketNames);
public:
explicit DeckEditorSettings(const QString &settingPath, QObject *parent = nullptr);