Merge branch 'master' into tooomm-qt5

This commit is contained in:
tooomm 2026-08-09 09:12:56 +02:00
commit bbf2412157
219 changed files with 7310 additions and 1824 deletions

View file

@ -44,7 +44,7 @@
#include <libcockatrice/protocol/pb/command_deck_upload.pb.h>
#include <libcockatrice/protocol/pb/response.pb.h>
#include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/settings/interface_settings.h>
#include <libcockatrice/settings/deck_editor_settings.h>
#include <libcockatrice/settings/paths_settings.h>
#include <libcockatrice/settings/recents_settings.h>
#include <libcockatrice/utility/string_limits.h>
@ -203,7 +203,7 @@ void AbstractTabDeckEditor::cleanDeckAndResetModified()
*/
AbstractTabDeckEditor::DeckOpenLocation AbstractTabDeckEditor::confirmOpen(const bool openInSameTabIfBlank)
{
if (SettingsCache::instance().interface().getOpenDeckInNewTab()) {
if (SettingsCache::instance().deckEditor().getOpenDeckInNewTab()) {
if (openInSameTabIfBlank && deckStateManager->isBlankNewDeck()) {
return SAME_TAB;
} else {

View file

@ -97,7 +97,7 @@ public slots:
protected:
void mousePressEvent(QMouseEvent *event) override;
void enterEvent(QEnterEvent *event) override;
void enterEvent(QEnterEvent *event) override; ///< Hover enter
void leaveEvent(QEvent *event) override;
private:

View file

@ -27,7 +27,7 @@
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/models/database/card/card_completer_proxy_model.h>
#include <libcockatrice/models/database/card/card_search_model.h>
#include <libcockatrice/settings/visual_deck_storage_settings.h>
#include <libcockatrice/settings/cards_display_settings.h>
#include <version_string.h>
TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor)
@ -132,8 +132,8 @@ void TabArchidekt::initializeUi()
// Settings
settingsButton = new SettingsButtonWidget(primaryToolbar);
cardSizeSlider = new CardSizeWidget(primaryToolbar, nullptr,
SettingsCache::instance().visualDeckStorage().getArchidektPreviewSize());
cardSizeSlider =
new CardSizeWidget(primaryToolbar, nullptr, SettingsCache::instance().cardsDisplay().getArchidektPreviewSize());
settingsButton->addSettingsWidget(cardSizeSlider);
// Assemble primary toolbar
@ -339,8 +339,8 @@ void TabArchidekt::connectSignals()
doSearch();
});
connect(cardSizeSlider, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance().visualDeckStorage(),
&VisualDeckStorageSettings::setArchidektPreviewCardSize);
connect(cardSizeSlider, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance().cardsDisplay(),
&CardsDisplaySettings::setArchidektPreviewCardSize);
// Search button triggers immediate search
connect(searchButton, &QPushButton::clicked, this, &TabArchidekt::doSearchImmediate);

View file

@ -0,0 +1,17 @@
#include "card_in_deck_request.h"
CardInDeckRequest CardInDeckRequest::fromJson(const QJsonObject &json)
{
CardInDeckRequest request;
request.card = json.value("card").toString();
request.quantity = json.value("quantity").toInt();
return request;
}
QJsonObject CardInDeckRequest::toJson() const
{
QJsonObject json;
json.insert("card", card);
json.insert("quantity", quantity);
return json;
}

View file

@ -0,0 +1,14 @@
#ifndef COCKATRICE_CARD_IN_DECK_REQUEST_H
#define COCKATRICE_CARD_IN_DECK_REQUEST_H
#include <QJsonObject>
struct CardInDeckRequest
{
static CardInDeckRequest fromJson(const QJsonObject &json);
QJsonObject toJson() const;
QString card;
int quantity;
};
#endif // COCKATRICE_CARD_IN_DECK_REQUEST_H

View file

@ -0,0 +1,26 @@
#include "commander_spellbook_card_result.h"
CommanderSpellbookCardResult CommanderSpellbookCardResult::fromJson(const QJsonObject &json)
{
CommanderSpellbookCardResult result;
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();
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

@ -0,0 +1,29 @@
#ifndef COCKATRICE_COMMANDER_SPELLBOOK_CARD_RESULT_H
#define COCKATRICE_COMMANDER_SPELLBOOK_CARD_RESULT_H
#include <QJsonObject>
#include <QString>
struct CommanderSpellbookCardResult
{
static CommanderSpellbookCardResult fromJson(const QJsonObject &json);
QString id;
QString name;
QString oracleId;
bool spoiler = false;
QString typeLine;
QString imageUriFrontPng;
QString imageUriFrontLarge;
QString imageUriFrontNormal;
QString imageUriFrontSmall;
QString imageUriFrontArtCrop;
QString imageUriBackPng;
QString imageUriBackLarge;
QString imageUriBackNormal;
QString imageUriBackSmall;
QString imageUriBackArtCrop;
};
#endif // COCKATRICE_COMMANDER_SPELLBOOK_CARD_RESULT_H

View file

@ -0,0 +1,95 @@
#include "commander_spellbook_deck_request.h"
#include <QJsonArray>
#include <libcockatrice/deck_list/tree/deck_list_card_node.h>
CommanderSpellbookDeckRequest CommanderSpellbookDeckRequest::fromJson(const QJsonObject &json)
{
CommanderSpellbookDeckRequest request;
// Main deck
const QJsonArray mainArray = json.value("main").toArray();
for (const QJsonValue &value : mainArray) {
if (!value.isObject()) {
continue;
}
request.mainDeck.append(CardInDeckRequest::fromJson(value.toObject()));
// Max size allowed by commanderspellbook
if (request.mainDeck.size() >= 600) {
break;
}
}
// Commanders
const QJsonArray commanderArray = json.value("commanders").toArray();
for (const QJsonValue &value : commanderArray) {
if (!value.isObject()) {
continue;
}
request.commanderDeck.append(CardInDeckRequest::fromJson(value.toObject()));
// Max size allowed by commanderspellbook
if (request.commanderDeck.size() >= 12) {
break;
}
}
return request;
}
QJsonObject CommanderSpellbookDeckRequest::toJson() const
{
QJsonObject json;
QJsonArray mainArray;
for (const CardInDeckRequest &card : mainDeck) {
mainArray.append(card.toJson());
}
QJsonArray commanderArray;
for (const CardInDeckRequest &card : commanderDeck) {
commanderArray.append(card.toJson());
}
json.insert("main", mainArray);
json.insert("commanders", commanderArray);
return json;
}
CommanderSpellbookDeckRequest CommanderSpellbookDeckRequest::fromDeckList(const DeckList &deck)
{
CommanderSpellbookDeckRequest request;
// --- Mainboard ---
const auto mainCards = deck.getCardNodes({DECK_ZONE_MAIN});
for (const DecklistCardNode *node : mainCards) {
if (!node) {
continue;
}
QJsonObject json;
json.insert("card", node->getName());
json.insert("quantity", node->getNumber());
request.mainDeck.append(CardInDeckRequest::fromJson(json));
// Max size allowed by commanderspellbook
if (request.mainDeck.size() >= 600) {
break;
}
}
// --- Commander (bannerCard) ---
const auto &metadata = deck.getMetadata();
if (!metadata.bannerCard.name.isEmpty()) {
QJsonObject json;
json.insert("card", metadata.bannerCard.name);
json.insert("quantity", 1);
request.commanderDeck.append(CardInDeckRequest::fromJson(json));
}
return request;
}

View file

@ -0,0 +1,19 @@
#ifndef COCKATRICE_COMMANDER_SPELLBOOK_DECK_REQUEST_H
#define COCKATRICE_COMMANDER_SPELLBOOK_DECK_REQUEST_H
#include "card_in_deck_request.h"
#include "libcockatrice/deck_list/deck_list.h"
#include <QJsonObject>
#include <QList>
struct CommanderSpellbookDeckRequest
{
static CommanderSpellbookDeckRequest fromJson(const QJsonObject &json);
static CommanderSpellbookDeckRequest fromDeckList(const DeckList &deck);
QJsonObject toJson() const;
QList<CardInDeckRequest> mainDeck; // maxItems: 600
QList<CardInDeckRequest> commanderDeck; // maxItems: 12
};
#endif // COCKATRICE_COMMANDER_SPELLBOOK_DECK_REQUEST_H

View file

@ -0,0 +1,93 @@
#include "commander_spellbook_estimate_bracket_result.h"
EstimateBracketResult EstimateBracketResult::fromJson(const QJsonObject &json)
{
EstimateBracketResult result;
result.bracketTag = json.value("bracketTag").toString();
//
// Cards
//
for (const auto &value : json.value("cards").toArray()) {
if (!value.isObject()) {
continue;
}
const QJsonObject obj = value.toObject();
CommanderSpellbookCardResult card = CommanderSpellbookCardResult::fromJson(obj.value("card").toObject());
if (obj.value("gameChanger").toBool()) {
result.gameChangerCards.append(card);
}
if (obj.value("massLandDenial").toBool()) {
result.massLandDenialCards.append(card);
}
if (obj.value("extraTurn").toBool()) {
result.extraTurnCards.append(card);
}
}
//
// Templates
//
for (const auto &value : json.value("templates").toArray()) {
if (!value.isObject()) {
continue;
}
const QJsonObject obj = value.toObject();
CommanderSpellbookVariantResult variant = CommanderSpellbookVariantResult::fromJson(obj);
if (obj.value("massLandDenial").toBool()) {
result.massLandDenialTemplates.append(variant);
}
if (obj.value("extraTurn").toBool()) {
result.extraTurnTemplates.append(variant);
}
}
//
// Combos
//
for (const auto &value : json.value("combos").toArray()) {
if (!value.isObject()) {
continue;
}
const QJsonObject obj = value.toObject();
CommanderSpellbookVariantResult combo = CommanderSpellbookVariantResult::fromJson(obj);
if (obj.value("massLandDenial").toBool()) {
result.massLandDenialCombos.append(combo);
}
if (obj.value("extraTurn").toBool()) {
result.extraTurnCombos.append(combo);
}
if (obj.value("lock").toBool()) {
result.lockCombos.append(combo);
}
if (obj.value("skipTurns").toBool()) {
result.skipTurnsCombos.append(combo);
}
if (obj.value("definitelyTwoCard").toBool()) {
result.definitelyTwoCardCombos.append(combo);
}
if (obj.value("arguablyTwoCard").toBool()) {
result.arguablyTwoCardCombos.append(combo);
}
}
return result;
}

View file

@ -0,0 +1,32 @@
#ifndef COCKATRICE_COMMANDER_SPELLBOOK_ESTIMATE_BRACKET_RESULT_H
#define COCKATRICE_COMMANDER_SPELLBOOK_ESTIMATE_BRACKET_RESULT_H
#include "commander_spellbook_card_result.h"
#include "commander_spellbook_variant_result.h"
#include <QJsonObject>
#include <QList>
struct EstimateBracketResult
{
static EstimateBracketResult fromJson(const QJsonObject &json);
QString bracketTag;
QList<CommanderSpellbookCardResult> gameChangerCards;
QList<CommanderSpellbookCardResult> massLandDenialCards;
QList<CommanderSpellbookCardResult> extraTurnCards;
QList<CommanderSpellbookVariantResult> massLandDenialTemplates;
QList<CommanderSpellbookVariantResult> extraTurnTemplates;
QList<CommanderSpellbookVariantResult> massLandDenialCombos;
QList<CommanderSpellbookVariantResult> extraTurnCombos;
QList<CommanderSpellbookVariantResult> lockCombos;
QList<CommanderSpellbookVariantResult> skipTurnsCombos;
QList<CommanderSpellbookVariantResult> definitelyTwoCardCombos;
QList<CommanderSpellbookVariantResult> arguablyTwoCardCombos;
};
#endif // COCKATRICE_COMMANDER_SPELLBOOK_ESTIMATE_BRACKET_RESULT_H

View file

@ -0,0 +1,35 @@
#include "commander_spellbook_variant_result.h"
CommanderSpellbookVariantResult CommanderSpellbookVariantResult::fromJson(const QJsonObject &json)
{
CommanderSpellbookVariantResult result;
result.id = json.value("id").toString();
result.status = json.value("status").toString();
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();
result.manaNeeded = json.value("manaNeeded").toArray();
result.manaValueNeeded = json.value("manaValueNeeded").toArray();
result.easyPrerequisites = json.value("easyPrerequisites").toArray();
result.notablePrerequisites = json.value("notablePrerequisites").toArray();
result.description = json.value("description").toString();
result.notes = json.value("notes").toString();
result.popularity = json.value("popularity").toDouble();
result.spoiler = json.value("spoiler").toBool();
result.bracketTag = json.value("bracketTag").toString();
result.legalities = json.value("legalities").toObject();
result.prices = json.value("prices").toObject();
result.variantCount = json.value("variantCount").toInt();
return result;
}

View file

@ -0,0 +1,39 @@
#ifndef COCKATRICE_COMMANDER_SPELLBOOK_VARIANT_RESULT_H
#define COCKATRICE_COMMANDER_SPELLBOOK_VARIANT_RESULT_H
#include <QJsonArray>
#include <QJsonObject>
struct CommanderSpellbookVariantResult
{
static CommanderSpellbookVariantResult fromJson(const QJsonObject &json);
QString id;
QString status;
QJsonArray uses;
QJsonArray cardRequires;
QJsonArray produces;
QJsonArray of;
QJsonArray includes;
QJsonArray manaNeeded;
QJsonArray manaValueNeeded;
QJsonArray easyPrerequisites;
QJsonArray notablePrerequisites;
QString description;
QString notes;
double popularity = 0.0;
bool spoiler = false;
QString bracketTag;
QJsonObject legalities;
QJsonObject prices;
int variantCount = 0;
};
#endif // COCKATRICE_COMMANDER_SPELLBOOK_VARIANT_RESULT_H

View file

@ -0,0 +1,53 @@
#include "commander_bracket_service.h"
#include "../../../../../client/settings/cache_settings.h"
#include <libcockatrice/settings/commander_bracket_settings.h>
CommanderBracketService &CommanderBracketService::instance()
{
static CommanderBracketService service;
return service;
}
CommanderBracketService::CommanderBracketService(QObject *parent) : QObject(parent)
{
connect(&CommanderSpellbookApiAccessor::instance(), &CommanderSpellbookApiAccessor::estimateBracketFinished, this,
&CommanderBracketService::onEstimateBracketFinished);
connect(&CommanderSpellbookApiAccessor::instance(), &CommanderSpellbookApiAccessor::estimateBracketError, this,
&CommanderBracketService::onEstimateBracketError);
}
quint64 CommanderBracketService::estimateBracket(const DeckList &deck, QObject *requester)
{
return CommanderSpellbookApiAccessor::instance().estimateBracket(deck, requester);
}
void CommanderBracketService::onEstimateBracketFinished(CommanderSpellbookApiAccessor::RequestId id,
QObject *requester,
const EstimateBracketResult &result)
{
CommanderBracketEstimate estimate;
estimate.bracketTag = result.bracketTag;
estimate.rawResult = result;
auto &brackets = SettingsCache::instance().commanderBrackets();
estimate.officialName = brackets.officialName(result.bracketTag);
estimate.displayName = brackets.displayName(result.bracketTag);
estimate.explanation = brackets.explanation(result.bracketTag);
emit estimateFinished(id, requester, estimate);
}
void CommanderBracketService::onEstimateBracketError(CommanderSpellbookApiAccessor::RequestId id,
QObject *requester,
const QString &error)
{
emit estimateError(id, requester, error);
}

View file

@ -0,0 +1,45 @@
#ifndef COCKATRICE_COMMANDER_BRACKET_SERVICE_H
#define COCKATRICE_COMMANDER_BRACKET_SERVICE_H
#include "commander_spellbook_api_accessor.h"
#include "libcockatrice/deck_list/deck_list.h"
#include <QObject>
struct CommanderBracketEstimate
{
QString bracketTag;
QString officialName;
QString displayName;
QString explanation;
EstimateBracketResult rawResult;
};
class CommanderBracketService : public QObject
{
Q_OBJECT
public:
static CommanderBracketService &instance();
quint64 estimateBracket(const DeckList &deck, QObject *requester);
signals:
void estimateFinished(quint64 requestId, QObject *requester, const CommanderBracketEstimate &estimate);
void estimateError(quint64 requestId, QObject *requester, const QString &error);
private slots:
void onEstimateBracketFinished(CommanderSpellbookApiAccessor::RequestId id,
QObject *requester,
const EstimateBracketResult &result);
void onEstimateBracketError(CommanderSpellbookApiAccessor::RequestId id, QObject *requester, const QString &error);
private:
explicit CommanderBracketService(QObject *parent = nullptr);
};
#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

@ -0,0 +1,75 @@
#include "commander_spellbook_api_accessor.h"
#include "api_response/commander_spellbook_deck_request.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkRequest>
#include <QUrl>
#include <version_string.h>
static const QUrl ESTIMATE_BRACKET_URL(QStringLiteral("https://backend.commanderspellbook.com/estimate-bracket"));
CommanderSpellbookApiAccessor &CommanderSpellbookApiAccessor::instance()
{
static CommanderSpellbookApiAccessor instance;
return instance;
}
CommanderSpellbookApiAccessor::CommanderSpellbookApiAccessor(QObject *parent) : QObject(parent)
{
}
CommanderSpellbookApiAccessor::RequestId CommanderSpellbookApiAccessor::estimateBracket(const DeckList &deck,
QObject *requester)
{
CommanderSpellbookDeckRequest deckRequest = CommanderSpellbookDeckRequest::fromDeckList(deck);
QJsonDocument doc(deckRequest.toJson());
QByteArray body = doc.toJson(QJsonDocument::Compact);
QNetworkRequest req(ESTIMATE_BRACKET_URL);
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
req.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
QNetworkReply *reply = network.post(req, body);
const RequestId id = nextRequestId++;
reply->setProperty("requestId", QVariant::fromValue(id));
reply->setProperty("requester", QVariant::fromValue(requester));
connect(reply, &QNetworkReply::finished, this, [this, reply]() { onEstimateReplyFinished(reply); });
return id;
}
void CommanderSpellbookApiAccessor::onEstimateReplyFinished(QNetworkReply *reply)
{
reply->deleteLater();
const RequestId id = reply->property("requestId").toULongLong();
QObject *requester = reply->property("requester").value<QObject *>();
if (!requester) {
// Requester died — silently drop
return;
}
if (reply->error() != QNetworkReply::NoError) {
emit estimateBracketError(id, requester, reply->errorString());
return;
}
QJsonParseError err;
QJsonDocument doc = QJsonDocument::fromJson(reply->readAll(), &err);
if (err.error != QJsonParseError::NoError || !doc.isObject()) {
emit estimateBracketError(id, requester, QStringLiteral("Invalid JSON response"));
return;
}
EstimateBracketResult result = EstimateBracketResult::fromJson(doc.object());
emit estimateBracketFinished(id, requester, result);
}

View file

@ -0,0 +1,37 @@
#ifndef COCKATRICE_COMMANDER_SPELLBOOK_API_ACCESSOR_H
#define COCKATRICE_COMMANDER_SPELLBOOK_API_ACCESSOR_H
#include "api_response/commander_spellbook_estimate_bracket_result.h"
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QObject>
#include <libcockatrice/deck_list/deck_list.h>
class CommanderSpellbookApiAccessor final : public QObject
{
Q_OBJECT
public:
static CommanderSpellbookApiAccessor &instance();
using RequestId = quint64;
RequestId estimateBracket(const DeckList &deck, QObject *requester);
signals:
void estimateBracketFinished(RequestId id, QObject *requester, const EstimateBracketResult &result);
void estimateBracketError(RequestId id, QObject *requester, const QString &errorMessage);
private:
explicit CommanderSpellbookApiAccessor(QObject *parent = nullptr);
Q_DISABLE_COPY_MOVE(CommanderSpellbookApiAccessor)
void onEstimateReplyFinished(QNetworkReply *reply);
QNetworkAccessManager network;
RequestId nextRequestId = 1;
};
#endif // COCKATRICE_COMMANDER_SPELLBOOK_API_ACCESSOR_H

View file

@ -0,0 +1,120 @@
#include "commander_spellbook_bracket_explainer.h"
static QString cardList(const QList<CommanderSpellbookCardResult> &cards, int max = 5)
{
QStringList names;
for (int i = 0; i < cards.size() && i < max; ++i) {
names << cards[i].name;
}
if (cards.size() > max) {
names << QString("and %1 more").arg(cards.size() - max);
}
return names.join(", ");
}
static QString comboCount(const QList<CommanderSpellbookVariantResult> &variants)
{
return QString::number(variants.size());
}
BracketExplanation BracketExplainer::explain(const EstimateBracketResult &r)
{
BracketExplanation out;
out.bracket = r.bracketTag;
if (!r.gameChangerCards.isEmpty()) {
BracketExplanationSection s;
s.title = "Game-changing cards";
s.bulletPoints << QString("Your deck contains %1 game-changing cards, such as %2.")
.arg(r.gameChangerCards.size())
.arg(cardList(r.gameChangerCards));
out.sections << s;
}
if (!r.extraTurnCards.isEmpty() || !r.extraTurnTemplates.isEmpty() || !r.extraTurnCombos.isEmpty()) {
BracketExplanationSection s;
s.title = "Extra turns";
if (!r.extraTurnCards.isEmpty()) {
s.bulletPoints << QString("The deck contains %1 extra-turn cards (%2).")
.arg(r.extraTurnCards.size())
.arg(cardList(r.extraTurnCards));
}
if (!r.extraTurnTemplates.isEmpty()) {
s.bulletPoints << QString("%1 extra-turn templates were identified.").arg(comboCount(r.extraTurnTemplates));
}
if (!r.extraTurnCombos.isEmpty()) {
s.bulletPoints
<< QString("%1 extra-turn combo variants were identified.").arg(comboCount(r.extraTurnCombos));
}
out.sections << s;
}
if (!r.massLandDenialCards.isEmpty() || !r.massLandDenialTemplates.isEmpty() || !r.massLandDenialCombos.isEmpty()) {
BracketExplanationSection s;
s.title = "Mass land denial";
if (!r.massLandDenialCards.isEmpty()) {
s.bulletPoints << QString("The deck contains %1 mass land denial cards (%2).")
.arg(r.massLandDenialCards.size())
.arg(cardList(r.massLandDenialCards));
}
if (!r.massLandDenialTemplates.isEmpty()) {
s.bulletPoints
<< QString("%1 mass land denial templates were identified.").arg(comboCount(r.massLandDenialTemplates));
}
if (!r.massLandDenialCombos.isEmpty()) {
s.bulletPoints << QString("%1 mass land denial combo variants were identified.")
.arg(comboCount(r.massLandDenialCombos));
}
out.sections << s;
}
if (!r.lockCombos.isEmpty() || !r.skipTurnsCombos.isEmpty()) {
BracketExplanationSection s;
s.title = "Lock pieces";
if (!r.lockCombos.isEmpty()) {
s.bulletPoints << QString("%1 lock combo variants were detected.").arg(comboCount(r.lockCombos));
}
if (!r.skipTurnsCombos.isEmpty()) {
s.bulletPoints << QString("%1 skip-turn combo variants were detected.").arg(comboCount(r.skipTurnsCombos));
}
out.sections << s;
}
if (!r.definitelyTwoCardCombos.isEmpty() || !r.arguablyTwoCardCombos.isEmpty()) {
BracketExplanationSection s;
s.title = "Two-card combos";
if (!r.definitelyTwoCardCombos.isEmpty()) {
s.bulletPoints << QString("%1 definite two-card combo variants were identified.")
.arg(comboCount(r.definitelyTwoCardCombos));
}
if (!r.arguablyTwoCardCombos.isEmpty()) {
s.bulletPoints << QString("%1 arguable two-card combo variants were identified.")
.arg(comboCount(r.arguablyTwoCardCombos));
}
out.sections << s;
}
return out;
}

View file

@ -0,0 +1,43 @@
#ifndef COCKATRICE_COMMANDER_SPELLBOOK_BRACKET_EXPLAINER_H
#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;
QStringList bulletPoints;
};
struct BracketExplanation
{
QString bracket;
QList<BracketExplanationSection> sections;
bool isEmpty() const
{
return sections.isEmpty();
}
};
class BracketExplainer
{
public:
static BracketExplanation explain(const EstimateBracketResult &result);
};
#endif // COCKATRICE_COMMANDER_SPELLBOOK_BRACKET_EXPLAINER_H

View file

@ -0,0 +1,62 @@
#include "handle_commander_brackets.h"
#include "../../../../../client/settings/cache_settings.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QUrl>
#include <libcockatrice/settings/commander_bracket_settings.h>
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)
{
}
void HandleCommanderBrackets::downloadBracketDefinitions()
{
if (reply) {
return;
}
reply = nam->get(QNetworkRequest(COMMANDER_BRACKET_JSON_URL));
connect(reply, &QNetworkReply::finished, this, &HandleCommanderBrackets::actFinishParsingDownloadedData);
}
void HandleCommanderBrackets::actFinishParsingDownloadedData()
{
reply = qobject_cast<QNetworkReply *>(sender());
if (reply->error() != QNetworkReply::NoError) {
emit sigBracketDefinitionsDownloadFailed(reply->error());
reply->deleteLater();
return;
}
QJsonParseError parseError;
auto document = QJsonDocument::fromJson(reply->readAll(), &parseError);
if (parseError.error != QJsonParseError::NoError) {
emit sigBracketDefinitionsDownloadFailed(QNetworkReply::UnknownContentError);
reply->deleteLater();
return;
}
updateBracketDefinitions(document.toVariant().toMap());
emit sigBracketDefinitionsDownloaded();
reply->deleteLater();
}
void HandleCommanderBrackets::updateBracketDefinitions(const QVariantMap &jsonMap)
{
const auto bracketList = jsonMap.value("brackets").toList();
SettingsCache::instance().commanderBrackets().saveDefinitions(bracketList);
SettingsCache::instance().commanderBrackets().reloadDefinitions(bracketList);
}

View file

@ -0,0 +1,31 @@
#ifndef COCKATRICE_HANDLE_COMMANDER_BRACKETS_H
#define COCKATRICE_HANDLE_COMMANDER_BRACKETS_H
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QObject>
class HandleCommanderBrackets : public QObject
{
Q_OBJECT
public:
explicit HandleCommanderBrackets(QObject *parent = nullptr);
void downloadBracketDefinitions();
signals:
void sigBracketDefinitionsDownloaded();
void sigBracketDefinitionsDownloadFailed(QNetworkReply::NetworkError error);
private slots:
void actFinishParsingDownloadedData();
private:
void updateBracketDefinitions(const QVariantMap &jsonMap);
QNetworkAccessManager *nam;
QNetworkReply *reply;
};
#endif // COCKATRICE_HANDLE_COMMANDER_BRACKETS_H

View file

@ -38,8 +38,7 @@ private:
protected slots:
void mousePressEvent(QMouseEvent *event) override;
void enterEvent(QEnterEvent *event) override;
void enterEvent(QEnterEvent *event) override; ///< Hover enter
void leaveEvent(QEvent *event) override;
};

View file

@ -25,7 +25,7 @@
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/models/database/card/card_completer_proxy_model.h>
#include <libcockatrice/models/database/card/card_search_model.h>
#include <libcockatrice/settings/visual_deck_storage_settings.h>
#include <libcockatrice/settings/cards_display_settings.h>
#include <version_string.h>
static bool canBeCommander(const CardInfoPtr &cardInfo)
@ -95,10 +95,9 @@ TabEdhRecMain::TabEdhRecMain(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor
settingsButton = new SettingsButtonWidget(this);
cardSizeSlider =
new CardSizeWidget(this, nullptr, SettingsCache::instance().visualDeckStorage().getEDHRecCardSize());
connect(cardSizeSlider, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance().visualDeckStorage(),
&VisualDeckStorageSettings::setEDHRecCardSize);
cardSizeSlider = new CardSizeWidget(this, nullptr, SettingsCache::instance().cardsDisplay().getEDHRecCardSize());
connect(cardSizeSlider, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance().cardsDisplay(),
&CardsDisplaySettings::setEDHRecCardSize);
settingsButton->addSettingsWidget(cardSizeSlider);

View file

@ -258,16 +258,13 @@ void TabGame::resetChatAndPhase()
void TabGame::emitUserEvent()
{
bool globalEvent = !game->getPlayerManager()->isSpectator() ||
SettingsCache::instance().interface().getSpectatorNotificationsEnabled();
SettingsCache::instance().userInterface().getSpectatorNotificationsEnabled();
emit userEvent(globalEvent);
updatePlayerListDockTitle();
}
TabGame::~TabGame()
{
if (replayWidget) {
delete replayWidget->replay;
}
for (auto &player : game->getPlayerManager()->getPlayers()) {
player->clear();
}
@ -1183,6 +1180,7 @@ void TabGame::createReplayDock(GameReplay *replay)
replayDock->setWidget(replayWidget);
replayDock->setFloating(false);
connect(replayWidget, &ReplayWidget::rewound, this, &TabGame::resetChatAndPhase);
connect(replayWidget, &ReplayWidget::eventReplayed, game->getGameEventHandler(),
[this](const auto &event, auto options) {
game->getGameEventHandler()->processGameEventContainer(event, nullptr, options);

View file

@ -242,7 +242,7 @@ void TabRoom::actClearChat()
void TabRoom::actOpenChatSettings()
{
DlgSettings settings(this);
settings.setTab(4);
settings.setTab(DlgSettings::MessagesPage);
settings.exec();
}
@ -280,6 +280,7 @@ void TabRoom::processListGamesEvent(const Event_ListGames &event)
for (int i = 0; i < gameListSize; ++i) {
gameSelector->processGameInfo(event.game_list(i));
}
emit gameListUpdated();
}
void TabRoom::processJoinRoomEvent(const Event_JoinRoom &event)

View file

@ -78,6 +78,7 @@ signals:
void openMessageDialog(const QString &userName, bool focus);
void maximizeClient();
void notIdle();
void gameListUpdated();
private slots:
void sendMessage();
void sayFinished(const Response &response);
@ -127,6 +128,10 @@ public:
{
return ownUser;
}
[[nodiscard]] GameSelector *getGameSelector() const
{
return gameSelector;
}
PendingCommand *prepareRoomCommand(const ::google::protobuf::Message &cmd);
void sendRoomCommand(PendingCommand *pend);

View file

@ -191,7 +191,10 @@ void TabServer::joinRoom(int id, bool setCurrent)
PendingCommand *pend = client->prepareSessionCommand(cmd);
pend->setExtraData(setCurrent);
connect(pend, &PendingCommand::finished, this, &TabServer::joinRoomFinished);
connect(pend, &PendingCommand::finished, this,
[this, id](const Response &r, const CommandContainer &c, const QVariant &v) {
joinRoomFinished(r, c, v, id);
});
client->sendCommand(pend);
@ -205,7 +208,8 @@ void TabServer::joinRoom(int id, bool setCurrent)
void TabServer::joinRoomFinished(const Response &r,
const CommandContainer & /*commandContainer*/,
const QVariant &extraData)
const QVariant &extraData,
int roomId)
{
switch (r.response_code()) {
case Response::RespOk:
@ -213,21 +217,25 @@ void TabServer::joinRoomFinished(const Response &r,
case Response::RespNameNotFound:
QMessageBox::critical(this, tr("Error"),
tr("Failed to join the server room: it doesn't exist on the server."));
emit roomJoinFailed(roomId);
return;
case Response::RespContextError:
QMessageBox::critical(
this, tr("Error"),
tr("The server thinks you are in the server room but your client is unable to display it. "
"Try restarting your client."));
emit roomJoinFailed(roomId);
return;
case Response::RespUserLevelTooLow:
QMessageBox::critical(this, tr("Error"),
tr("You do not have the required permission to join this server room."));
emit roomJoinFailed(roomId);
return;
default:
QMessageBox::critical(
this, tr("Error"),
tr("Failed to join the server room due to an unknown error: %1.").arg(r.response_code()));
emit roomJoinFailed(roomId);
return;
}

View file

@ -49,10 +49,13 @@ class TabServer : public Tab
Q_OBJECT
signals:
void roomJoined(const ServerInfo_Room &info, bool setCurrent);
void roomJoinFailed(int roomId);
private slots:
void processServerMessageEvent(const Event_ServerMessage &event);
void joinRoom(int id, bool setCurrent);
void joinRoomFinished(const Response &resp, const CommandContainer &commandContainer, const QVariant &extraData);
void joinRoomFinished(const Response &resp,
const CommandContainer &commandContainer,
const QVariant &extraData,
int roomId);
private:
AbstractClient *client;
@ -62,6 +65,7 @@ private:
public:
TabServer(TabSupervisor *_tabSupervisor, AbstractClient *_client);
void joinRoom(int id, bool setCurrent);
void retranslateUi() override;
[[nodiscard]] QString getTabText() const override
{

View file

@ -39,9 +39,21 @@
#include <libcockatrice/protocol/pb/serverinfo_room.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
#include <libcockatrice/settings/chat_settings.h>
#include <libcockatrice/settings/deck_editor_settings.h>
#include <libcockatrice/settings/interface_settings.h>
#include <libcockatrice/settings/tabs_settings.h>
#include <libcockatrice/settings/visual_deck_storage_settings.h>
QRect MacOSTabFixStyle::subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const
{
if (element != SE_TabBarTabText) {
return QProxyStyle::subElementRect(element, option, widget);
}
// Skip over QProxyStyle handling subElementRect,
// This fixes an issue on OSX where the labels for tabs with a button and an icon
// get cut-off too early
return QCommonStyle::subElementRect(element, option, widget);
}
CloseButton::CloseButton(QWidget *parent) : QAbstractButton(parent)
{
@ -106,6 +118,12 @@ TabSupervisor::TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget *
setMovable(true);
setIconSize(QSize(15, 15));
#if defined(Q_OS_MAC)
// This is necessary to fix an issue on macOS,
// where tabs with icons and buttons get drawn incorrectly
tabBar()->setStyle(new MacOSTabFixStyle);
#endif
userListManager = new UserListManager(client, this);
// connect tab changes
@ -565,6 +583,11 @@ void TabSupervisor::actTabServer(bool checked)
void TabSupervisor::openTabServer()
{
SettingsCache::instance().tabs().setTabServerOpen(true);
if (tabServer) {
return;
}
tabServer = new TabServer(this, client);
connect(tabServer, &TabServer::roomJoined, this, &TabSupervisor::addRoomTab);
myAddTab(tabServer, aTabServer);
@ -888,7 +911,7 @@ void TabSupervisor::talkLeft(TabMessage *tab)
*/
void TabSupervisor::openDeckInNewTab(const LoadedDeck &deckToOpen)
{
int type = SettingsCache::instance().visualDeckStorage().getDefaultDeckEditorType();
int type = SettingsCache::instance().deckEditor().getDefaultDeckEditorType();
switch (type) {
case ClassicDeckEditor:
addDeckEditorTab(deckToOpen);
@ -987,7 +1010,7 @@ void TabSupervisor::tabUserEvent(bool globalEvent)
tab->setContentsChanged(true);
setTabIcon(indexOf(tab), QPixmap("theme:icons/tab_changed"));
}
if (globalEvent && SettingsCache::instance().interface().getNotificationsEnabled()) {
if (globalEvent && SettingsCache::instance().userInterface().getNotificationsEnabled()) {
QApplication::alert(this);
}
}
@ -1082,7 +1105,7 @@ void TabSupervisor::processUserJoined(const ServerInfo_User &userInfoJoined)
}
}
if (SettingsCache::instance().interface().getBuddyConnectNotificationsEnabled()) {
if (SettingsCache::instance().userInterface().getBuddyConnectNotificationsEnabled()) {
QApplication::alert(this);
this->actShowPopup(tr("Your buddy %1 has signed on!").arg(userName));
}

View file

@ -140,6 +140,10 @@ public:
{
return userListManager;
}
[[nodiscard]] TabServer *getTabServer() const
{
return tabServer;
}
[[nodiscard]] const QMap<int, TabRoom *> &getRoomTabs() const
{
return roomTabs;
@ -171,6 +175,7 @@ public slots:
void maximizeMainWindow();
void actTabVisualDeckStorage(bool checked);
void actTabReplays(bool checked);
void openTabServer();
private slots:
void refreshShortcuts();
@ -183,7 +188,6 @@ private slots:
void openTabVisualDeckStorage();
void openTabHome();
void openTabServer();
void openTabAccount();
void openTabDeckStorage();
void openTabReplays();