[DeckEditor] Add a prompt for commanderSpellbook integration

Took 2 hours 20 minutes

Took 5 seconds
This commit is contained in:
Lukas Brübach 2025-12-16 10:57:06 +01:00 committed by Brübach, Lukas
parent 9759cdd07f
commit a3de2dfd46
6 changed files with 286 additions and 5 deletions

View file

@ -1,5 +1,6 @@
#include "cache_settings.h"
#include "../../interface/widgets/dialogs/dlg_settings.h"
#include "../network/update/client/release_channel.h"
#include "card_counter_settings.h"
#include "version_string.h"
@ -303,6 +304,14 @@ SettingsCache::SettingsCache()
deckEditorBannerCardComboBoxVisible =
settings->value("interface/deckeditorbannercardcomboboxvisible", true).toBool();
deckEditorTagsWidgetVisible = settings->value("interface/deckeditortagswidgetvisible", true).toBool();
deckEditorCommanderSpellbookIntegrationEnabled =
settings
->value("interface/deck_editor/commander_spellbook_integration/enabled",
deckEditorCommanderSpellbookIntegrationEnabledIndexUnprompted)
.toInt();
deckEditorCommanderSpellbookIntegrationUseOfficialBracketNames =
settings->value("interface/deck_editor/commander_spellbook_integration/use_official_bracket_names", false)
.toBool();
visualDeckStorageCardSize = settings->value("interface/visualdeckstoragecardsize", 100).toInt();
visualDeckStorageSortingOrder = settings->value("interface/visualdeckstoragesortingorder", 0).toInt();
visualDeckStorageShowFolders = settings->value("interface/visualdeckstorageshowfolders", true).toBool();
@ -798,6 +807,26 @@ void SettingsCache::setDeckEditorTagsWidgetVisible(QT_STATE_CHANGED_T _deckEdito
emit deckEditorTagsWidgetVisibleChanged(deckEditorTagsWidgetVisible);
}
void SettingsCache::setDeckEditorCommanderSpellbookIntegrationEnabled(
int _deckEditorCommanderSpellbookIntegrationEnabled)
{
deckEditorCommanderSpellbookIntegrationEnabled = _deckEditorCommanderSpellbookIntegrationEnabled;
settings->setValue("interface/deck_editor/commander_spellbook_integration/enabled",
deckEditorCommanderSpellbookIntegrationEnabled);
emit deckEditorCommanderSpellbookIntegrationEnabledChanged(deckEditorCommanderSpellbookIntegrationEnabled);
}
void SettingsCache::setDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames(
bool _deckEditorCommanderSpellbookIntegrationUseOfficialBracketNames)
{
deckEditorCommanderSpellbookIntegrationUseOfficialBracketNames =
_deckEditorCommanderSpellbookIntegrationUseOfficialBracketNames;
settings->setValue("interface/deck_editor/commander_spellbook_integration/use_official_bracket_names",
deckEditorCommanderSpellbookIntegrationUseOfficialBracketNames);
emit deckEditorCommanderSpellbookIntegrationUseOfficialBracketNamesChanged(
deckEditorCommanderSpellbookIntegrationUseOfficialBracketNames);
}
void SettingsCache::setVisualDeckStorageSortingOrder(int _visualDeckStorageSortingOrder)
{
visualDeckStorageSortingOrder = _visualDeckStorageSortingOrder;

View file

@ -156,6 +156,8 @@ signals:
void printingSelectorNavigationButtonsVisibleChanged();
void deckEditorBannerCardComboBoxVisibleChanged(bool _visible);
void deckEditorTagsWidgetVisibleChanged(bool _visible);
void deckEditorCommanderSpellbookIntegrationEnabledChanged(int _enabled);
void deckEditorCommanderSpellbookIntegrationUseOfficialBracketNamesChanged(bool _useOfficialBracketNames);
void visualDeckStorageShowTagFilterChanged(bool _visible);
void visualDeckStorageDefaultTagsListChanged();
void visualDeckStorageShowColorIdentityChanged(bool _visible);
@ -250,6 +252,8 @@ private:
bool printingSelectorNavigationButtonsVisible;
bool deckEditorBannerCardComboBoxVisible;
bool deckEditorTagsWidgetVisible;
int deckEditorCommanderSpellbookIntegrationEnabled;
bool deckEditorCommanderSpellbookIntegrationUseOfficialBracketNames;
int visualDeckStorageSortingOrder;
bool visualDeckStorageShowFolders;
bool visualDeckStorageShowColorIdentity;
@ -723,6 +727,14 @@ public:
{
return openDeckInNewTab;
}
[[nodiscard]] int getDeckEditorCommanderSpellbookIntegrationEnabled() const
{
return deckEditorCommanderSpellbookIntegrationEnabled;
}
[[nodiscard]] bool getDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames() const
{
return deckEditorCommanderSpellbookIntegrationUseOfficialBracketNames;
}
[[nodiscard]] int getRewindBufferingMs() const
{
return rewindBufferingMs;
@ -1046,6 +1058,9 @@ public slots:
void setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED_T _navigationButtonsVisible);
void setDeckEditorBannerCardComboBoxVisible(QT_STATE_CHANGED_T _deckEditorBannerCardComboBoxVisible);
void setDeckEditorTagsWidgetVisible(QT_STATE_CHANGED_T _deckEditorTagsWidgetVisible);
void setDeckEditorCommanderSpellbookIntegrationEnabled(int _deckEditorCommanderSpellbookIntegrationEnabled);
void setDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames(
bool _deckEditorCommanderSpellbookIntegrationUseOfficialBracketNames);
void setVisualDeckStorageSortingOrder(int _visualDeckStorageSortingOrder);
void setVisualDeckStorageShowFolders(QT_STATE_CHANGED_T value);
void setVisualDeckStorageShowTagFilter(QT_STATE_CHANGED_T _showTags);

View file

@ -1,6 +1,7 @@
#include "deck_editor_deck_dock_widget.h"
#include "../../../client/settings/cache_settings.h"
#include "../dialogs/dlg_settings.h"
#include "../tabs/api/commander_spellbook/commander_spellbook_api_accessor.h"
#include "../tabs/api/commander_spellbook/commander_spellbook_bracket_explainer.h"
#include "deck_list_style_proxy.h"
@ -8,8 +9,10 @@
#include <QComboBox>
#include <QDockWidget>
#include <QFormLayout>
#include <QHeaderView>
#include <QLabel>
#include <QMessageBox>
#include <QSplitter>
#include <QTextEdit>
#include <libcockatrice/card/database/card_database_manager.h>
@ -310,6 +313,92 @@ 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().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().setDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames(useOfficial);
}
// Persist integration mode
if (clickedButton == disableBtn) {
SettingsCache::instance().setDeckEditorCommanderSpellbookIntegrationEnabled(
deckEditorCommanderSpellbookIntegrationEnabledIndexDisabled);
return false;
}
if (clickedButton == enableBtn) {
SettingsCache::instance().setDeckEditorCommanderSpellbookIntegrationEnabled(
deckEditorCommanderSpellbookIntegrationEnabledIndexEnabled);
return true;
}
if (clickedButton == automaticBtn) {
SettingsCache::instance().setDeckEditorCommanderSpellbookIntegrationEnabled(
deckEditorCommanderSpellbookIntegrationEnabledIndexAutomatic);
return true;
}
return false;
}
void DeckEditorDeckDockWidget::requestBracketEstimate()
{
bracketRefreshButton->setEnabled(false);
@ -334,7 +423,11 @@ void DeckEditorDeckDockWidget::onEstimateBracketFinished(CommanderSpellbookApiAc
lastBracketExplanation = explainer.explain(result);
// Display bracket
bracketValueLabel->setText(CommanderSpellbookBracketTag::bracketTagToOfficialString(result.bracketTag));
if (SettingsCache::instance().getDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames()) {
bracketValueLabel->setText(CommanderSpellbookBracketTag::bracketTagToOfficialString(result.bracketTag));
} else {
bracketValueLabel->setText(CommanderSpellbookBracketTag::bracketTagToString(result.bracketTag));
}
bracketRefreshButton->setEnabled(true);
// Build tooltip
@ -383,11 +476,16 @@ void DeckEditorDeckDockWidget::initializeFormats()
emit deckModified();
const bool isCommander = (formatKey.compare("commander", Qt::CaseInsensitive) == 0);
const bool commanderSpellbookIntegrationEnabled =
SettingsCache::instance().getDeckEditorCommanderSpellbookIntegrationEnabled() !=
deckEditorCommanderSpellbookIntegrationEnabledIndexDisabled;
bracketLabel->setVisible(isCommander);
bracketValueLabel->setVisible(isCommander);
bracketInfoButton->setVisible(isCommander);
bracketRefreshButton->setVisible(isCommander);
const bool bracketVisible = isCommander && commanderSpellbookIntegrationEnabled;
bracketLabel->setVisible(bracketVisible);
bracketValueLabel->setVisible(bracketVisible);
bracketInfoButton->setVisible(bracketVisible);
bracketRefreshButton->setVisible(bracketVisible);
if (!isCommander) {
bracketValueLabel->setText("-");
@ -413,6 +511,23 @@ void DeckEditorDeckDockWidget::maybeAutoEstimateBracket()
return;
}
int mode = SettingsCache::instance().getDeckEditorCommanderSpellbookIntegrationEnabled();
if (mode == deckEditorCommanderSpellbookIntegrationEnabledIndexUnprompted) {
if (!promptCommanderSpellbookIntegration()) {
bracketLabel->setVisible(false);
bracketValueLabel->setVisible(false);
bracketInfoButton->setVisible(false);
bracketRefreshButton->setVisible(false);
return; // user chose Disabled
}
// user chose Enabled or Automatic → fall through
}
mode = SettingsCache::instance().getDeckEditorCommanderSpellbookIntegrationEnabled();
if (mode != deckEditorCommanderSpellbookIntegrationEnabledIndexAutomatic) {
return;
}
// Avoid firing if we already have a result or a request in flight
if (!bracketRefreshButton->isEnabled()) {
return;

View file

@ -35,6 +35,7 @@ public:
QTreeView *deckView;
QComboBox *bannerCardComboBox;
void createDeckDock();
bool promptCommanderSpellbookIntegration();
void requestBracketEstimate();
ExactCard getCurrentCard();
void retranslateUi();

View file

@ -895,6 +895,56 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage()
connect(&defaultDeckEditorTypeSelector, QOverload<int>::of(&QComboBox::currentIndexChanged),
&SettingsCache::instance(), &SettingsCache::setDefaultDeckEditorType);
deckEditorCommanderSpellBookIntegrationUseOfficialBracketNamesExplainer.setText("?");
deckEditorCommanderSpellBookIntegrationUseOfficialBracketNamesExplainer.setAutoRaise(true);
deckEditorCommanderSpellBookIntegrationUseOfficialBracketNamesExplainer.setEnabled(false);
// Add items with userData = internal enum
deckEditorCommanderSpellbookIntegrationEnabledSelector.addItem(
tr("Disabled"), deckEditorCommanderSpellbookIntegrationEnabledIndexDisabled);
deckEditorCommanderSpellbookIntegrationEnabledSelector.addItem(
tr("Enabled"), deckEditorCommanderSpellbookIntegrationEnabledIndexEnabled);
deckEditorCommanderSpellbookIntegrationEnabledSelector.addItem(
tr("Automatic"), deckEditorCommanderSpellbookIntegrationEnabledIndexAutomatic);
int storedMode = SettingsCache::instance().getDeckEditorCommanderSpellbookIntegrationEnabled();
for (int i = 0; i < deckEditorCommanderSpellbookIntegrationEnabledSelector.count(); ++i) {
if (deckEditorCommanderSpellbookIntegrationEnabledSelector.itemData(i).toInt() == storedMode) {
deckEditorCommanderSpellbookIntegrationEnabledSelector.setCurrentIndex(i);
break;
}
}
connect(&deckEditorCommanderSpellbookIntegrationEnabledSelector,
QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) {
int mode = deckEditorCommanderSpellbookIntegrationEnabledSelector.itemData(index).toInt();
SettingsCache::instance().setDeckEditorCommanderSpellbookIntegrationEnabled(mode);
updateCommanderSpellbookUiState();
});
deckEditorCommanderSpellbookIntegrationBracketNamingSelector.addItem(
tr("CommanderSpellbook bracket names")); // index 0 = false
deckEditorCommanderSpellbookIntegrationBracketNamingSelector.addItem(
tr("Official Commander bracket names (approximate)")); // index 1 = true
deckEditorCommanderSpellbookIntegrationBracketNamingSelector.setCurrentIndex(
SettingsCache::instance().getDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames() ? 1 : 0);
connect(&deckEditorCommanderSpellbookIntegrationBracketNamingSelector,
QOverload<int>::of(&QComboBox::currentIndexChanged), &SettingsCache::instance(), [](int index) {
SettingsCache::instance().setDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames(index == 1);
});
updateCommanderSpellbookUiState();
auto *labelLayout = new QHBoxLayout;
labelLayout->setContentsMargins(0, 0, 0, 0);
labelLayout->addWidget(&deckEditorCommanderSpellbookIntegrationUseOfficialBracketNamesLabel);
labelLayout->addWidget(&deckEditorCommanderSpellBookIntegrationUseOfficialBracketNamesExplainer);
auto *labelWidget = new QWidget;
labelWidget->setLayout(labelLayout);
auto *deckEditorGrid = new QGridLayout;
deckEditorGrid->addWidget(&openDeckInNewTabCheckBox, 0, 0);
deckEditorGrid->addWidget(&visualDeckStorageInGameCheckBox, 1, 0);
@ -903,6 +953,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(labelWidget, 6, 0);
deckEditorGrid->addWidget(&deckEditorCommanderSpellbookIntegrationBracketNamingSelector, 6, 1);
deckEditorGroupBox = new QGroupBox;
deckEditorGroupBox->setLayout(deckEditorGrid);
@ -945,6 +999,27 @@ void UserInterfaceSettingsPage::setNotificationEnabled(QT_STATE_CHANGED_T i)
}
}
void UserInterfaceSettingsPage::updateCommanderSpellbookUiState()
{
const int mode = SettingsCache::instance().getDeckEditorCommanderSpellbookIntegrationEnabled();
const bool enabled = mode != deckEditorCommanderSpellbookIntegrationEnabledIndexDisabled &&
mode != deckEditorCommanderSpellbookIntegrationEnabledIndexUnprompted;
deckEditorCommanderSpellbookIntegrationBracketNamingSelector.setEnabled(enabled);
deckEditorCommanderSpellBookIntegrationUseOfficialBracketNamesExplainer.setEnabled(enabled);
deckEditorCommanderSpellbookIntegrationUseOfficialBracketNamesLabel.setVisible(enabled);
deckEditorCommanderSpellBookIntegrationUseOfficialBracketNamesExplainer.setVisible(enabled);
deckEditorCommanderSpellbookIntegrationBracketNamingSelector.setVisible(enabled);
if (enabled) {
// Sync selector with the current stored bool
const bool useOfficial =
SettingsCache::instance().getDeckEditorCommanderSpellbookIntegrationUseOfficialBracketNames();
deckEditorCommanderSpellbookIntegrationBracketNamingSelector.setCurrentIndex(useOfficial ? 1 : 0);
}
}
void UserInterfaceSettingsPage::retranslateUi()
{
generalGroupBox->setTitle(tr("General interface settings"));
@ -977,6 +1052,22 @@ void UserInterfaceSettingsPage::retranslateUi()
defaultDeckEditorTypeLabel.setText(tr("Default deck editor type"));
defaultDeckEditorTypeSelector.setItemText(TabSupervisor::ClassicDeckEditor, tr("Classic Deck Editor"));
defaultDeckEditorTypeSelector.setItemText(TabSupervisor::VisualDeckEditor, tr("Visual Deck Editor"));
deckEditorCommanderSpellbookIntegrationEnabledLabel.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(
0, CommanderBracketNames::CommanderSpellbookBracketNames);
deckEditorCommanderSpellbookIntegrationBracketNamingSelector.setItemText(
1, CommanderBracketNames::OfficialCommanderBracketNames);
deckEditorCommanderSpellBookIntegrationUseOfficialBracketNamesExplainer.setToolTip(
CommanderBracketNames::Explainer);
replayGroupBox->setTitle(tr("Replay settings"));
rewindBufferingMsLabel.setText(tr("Buffer time for backwards skip via shortcut:"));
rewindBufferingMsBox.setSuffix(" ms");

View file

@ -15,6 +15,7 @@
#include <QLoggingCategory>
#include <QPushButton>
#include <QSpinBox>
#include <QToolButton>
#include <libcockatrice/utility/macros.h>
inline Q_LOGGING_CATEGORY(DlgSettingsLog, "dlg_settings");
@ -154,11 +155,35 @@ public:
void retranslateUi() override;
};
enum deckEditorCommanderSpellbookIntegrationEnabledIndex
{
deckEditorCommanderSpellbookIntegrationEnabledIndexDisabled,
deckEditorCommanderSpellbookIntegrationEnabledIndexEnabled,
deckEditorCommanderSpellbookIntegrationEnabledIndexAutomatic,
deckEditorCommanderSpellbookIntegrationEnabledIndexUnprompted,
};
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
private slots:
void setNotificationEnabled(QT_STATE_CHANGED_T);
void updateCommanderSpellbookUiState();
private:
QCheckBox notificationsEnabledCheckBox;
@ -180,6 +205,11 @@ private:
QCheckBox visualDeckStorageSelectionAnimationCheckBox;
QLabel defaultDeckEditorTypeLabel;
QComboBox defaultDeckEditorTypeSelector;
QLabel deckEditorCommanderSpellbookIntegrationEnabledLabel;
QComboBox deckEditorCommanderSpellbookIntegrationEnabledSelector;
QLabel deckEditorCommanderSpellbookIntegrationUseOfficialBracketNamesLabel;
QToolButton deckEditorCommanderSpellBookIntegrationUseOfficialBracketNamesExplainer;
QComboBox deckEditorCommanderSpellbookIntegrationBracketNamingSelector;
QLabel rewindBufferingMsLabel;
QSpinBox rewindBufferingMsBox;
QGroupBox *generalGroupBox;