Merge remote-tracking branch 'upstream/master' into first-run-tutorial

This commit is contained in:
Lukas Brübach 2026-10-11 15:14:26 +02:00
commit f5826d9d6f
1395 changed files with 67177 additions and 10446 deletions

View file

@ -71,3 +71,5 @@ target_include_directories(
)
target_link_libraries(libcockatrice_settings PUBLIC libcockatrice_card libcockatrice_utility ${QT_CORE_MODULE})
target_precompile_headers(libcockatrice_settings PRIVATE "${CMAKE_SOURCE_DIR}/cmake/pch/qtcore_pch.h")

View file

@ -1,5 +1,11 @@
#include "appearance_settings.h"
#include "libcockatrice/settings/settings_manager.h"
#include <QVariant>
class QObject;
AppearanceSettings::AppearanceSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "appearance.ini", "appearance", QString(), parent)
{
@ -69,3 +75,25 @@ void AppearanceSettings::setHomeTabDisplayCardName(bool _displayCardName)
setValue(_displayCardName, "homeTabDisplayCardName");
emit homeTabDisplayCardNameChanged();
}
bool AppearanceSettings::getHomeTabBackgroundDim() const
{
return getValue("homeTabBackgroundDim", QString(), QString(), true).toBool();
}
void AppearanceSettings::setHomeTabBackgroundDim(bool _dimBackground)
{
setValue(_dimBackground, "homeTabBackgroundDim");
emit homeTabBackgroundDimChanged();
}
int AppearanceSettings::getHomeTabButtonColorSourceIndex() const
{
return getValue("homeTabButtonColorSource", "", "", 0).toInt();
}
void AppearanceSettings::setHomeTabButtonColorSourceIndex(int index)
{
setValue(index, "homeTabButtonColorSource");
emit homeTabButtonColorChanged();
}

View file

@ -9,6 +9,11 @@
#include "settings_manager.h"
#include <QString>
#include <qtmetamacros.h>
class QObject;
class AppearanceSettings : public SettingsManager
{
Q_OBJECT
@ -27,6 +32,10 @@ public:
void setHomeTabBackgroundShuffleFrequency(int _frequency);
[[nodiscard]] bool getHomeTabDisplayCardName() const;
void setHomeTabDisplayCardName(bool _displayCardName);
[[nodiscard]] bool getHomeTabBackgroundDim() const;
void setHomeTabBackgroundDim(bool _dimBackground);
[[nodiscard]] int getHomeTabButtonColorSourceIndex() const;
void setHomeTabButtonColorSourceIndex(int index);
signals:
void themeNameChanged();
@ -34,6 +43,8 @@ signals:
void homeTabBackgroundSourceChanged();
void homeTabBackgroundShuffleFrequencyChanged();
void homeTabDisplayCardNameChanged();
void homeTabBackgroundDimChanged();
void homeTabButtonColorChanged();
public:
explicit AppearanceSettings(const QString &settingPath, QObject *parent = nullptr);

View file

@ -1,5 +1,11 @@
#include "cache_storage_settings.h"
#include "libcockatrice/settings/settings_manager.h"
#include <QVariant>
class QObject;
CacheStorageSettings::CacheStorageSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "cache_storage.ini", "cache_storage", QString(), parent)
{

View file

@ -3,7 +3,11 @@
#include "settings_manager.h"
#include <QString>
#include <libcockatrice/interfaces/interface_cache_storage_settings_provider.h>
#include <qtmetamacros.h>
class QObject;
// In MB (Increments of 64)
#define PIXMAPCACHE_SIZE_DEFAULT 2048

View file

@ -1,6 +1,16 @@
#include "card_database_settings.h"
#include "libcockatrice/interfaces/interface_card_set_priority_controller.h"
#include "libcockatrice/settings/settings_manager.h"
#include <QList>
#include <QMutexLocker>
#include <QSettings>
#include <QStringList>
#include <QVariant>
#include <functional>
class QObject;
CardDatabaseSettings::CardDatabaseSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "cardDatabase.ini", QString(), QString(), parent)
@ -78,6 +88,20 @@ ICardSetPriorityController::SetOptions CardDatabaseSettings::getSetOptions(QStri
return setOptionsCache.value(shortName);
}
QStringList CardDatabaseSettings::getEnabledSetNames() const
{
QMutexLocker lock(&setOptionsMutex);
ensureSetOptionsLoaded();
QStringList names;
for (auto it = setOptionsCache.constBegin(); it != setOptionsCache.constEnd(); ++it) {
if (it.value().enabled) {
names << it.key();
}
}
names.sort();
return names;
}
void CardDatabaseSettings::saveSets(const QVector<ICardSetPriorityController::SetSaveData> &data)
{
batchWrite([&](QSettings &s) {

View file

@ -12,7 +12,12 @@
#include <QHash>
#include <QMutex>
#include <QString>
#include <QVector>
#include <libcockatrice/interfaces/interface_card_set_priority_controller.h>
#include <qtmetamacros.h>
class QObject;
class CardDatabaseSettings : public SettingsManager, public ICardSetPriorityController
{
@ -30,6 +35,8 @@ public:
SetOptions getSetOptions(QString shortName) const override;
QStringList getEnabledSetNames() const override;
void saveSets(const QVector<SetSaveData> &data) override;
private:

View file

@ -1,5 +1,12 @@
#include "card_override_settings.h"
#include "libcockatrice/settings/settings_manager.h"
#include "libcockatrice/utility/card_ref.h"
#include <QVariant>
class QObject;
CardOverrideSettings::CardOverrideSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "cardPreferenceOverrides.ini", "cards", QString(), parent)
{

View file

@ -9,8 +9,11 @@
#include "settings_manager.h"
#include <QObject>
#include <libcockatrice/utility/card_ref.h>
#include <QString>
#include <qtmetamacros.h>
class QObject;
struct CardRef;
class CardOverrideSettings : public SettingsManager
{

View file

@ -1,5 +1,13 @@
#include "cards_display_settings.h"
#include "libcockatrice/card/card_localization.h"
#include "libcockatrice/settings/settings_manager.h"
#include <QSharedPointer>
#include <QVariant>
class QObject;
CardsDisplaySettings::CardsDisplaySettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "cards_display.ini", "cards", QString(), parent)
{
@ -35,11 +43,6 @@ int CardsDisplaySettings::getPrintingSelectorCardSize() const
return getValue("printingSelector", "cards", "cardSize", 100).toInt();
}
bool CardsDisplaySettings::getIncludeRebalancedCards() const
{
return getValue("includerebalancedcards", QString(), QString(), true).toBool();
}
bool CardsDisplaySettings::getPrintingSelectorNavigationButtonsVisible() const
{
return getValue("navigationButtonsVisible", "cards", "printingSelector", true).toBool();
@ -50,6 +53,11 @@ bool CardsDisplaySettings::getTapAnimation() const
return getValue("tapAnimation", QString(), QString(), true).toBool();
}
bool CardsDisplaySettings::getArrowDrawAnimation() const
{
return getValue("arrowDrawAnimation", QString(), QString(), true).toBool();
}
bool CardsDisplaySettings::getAutoRotateSidewaysLayoutCards() const
{
return getValue("autoRotateSidewaysLayoutCards", QString(), QString(), true).toBool();
@ -85,6 +93,11 @@ int CardsDisplaySettings::getVisualDeckEditorCardSize() const
return getValue("visualDeckEditor", "cards", "cardSize", 100).toInt();
}
bool CardsDisplaySettings::getVisualDeckEditorShowCardCounts() const
{
return getValue("visualDeckEditorShowCardCounts", QString(), QString(), true).toBool();
}
int CardsDisplaySettings::getEDHRecCardSize() const
{
return getValue("edhrec", "cards", "cardSize", 100).toInt();
@ -100,6 +113,16 @@ int CardsDisplaySettings::getSampleHandSize() const
return getValue("sampleHandSize", "cards", "cardSize", 7).toInt();
}
QString CardsDisplaySettings::getCardLang() const
{
return getValue("cardLang", QString(), QString(), "en").toString();
}
int CardsDisplaySettings::getCardSearchLanguage() const
{
return getValue("cardSearchLanguage", QString(), QString(), static_cast<int>(SearchLanguageMode::English)).toInt();
}
void CardsDisplaySettings::setDisplayCardNames(bool _displayCardNames)
{
setValue(_displayCardNames, "displayCardNames");
@ -139,15 +162,6 @@ void CardsDisplaySettings::setPrintingSelectorCardSize(int _printingSelectorCard
emit printingSelectorCardSizeChanged();
}
void CardsDisplaySettings::setIncludeRebalancedCards(bool _includeRebalancedCards)
{
if (_includeRebalancedCards == getIncludeRebalancedCards()) {
return;
}
setValue(_includeRebalancedCards, "includerebalancedcards");
emit includeRebalancedCardsChanged(_includeRebalancedCards);
}
void CardsDisplaySettings::setPrintingSelectorNavigationButtonsVisible(bool _navigationButtonsVisible)
{
setValue(_navigationButtonsVisible, "navigationButtonsVisible", "cards", "printingSelector");
@ -159,6 +173,11 @@ void CardsDisplaySettings::setTapAnimation(bool _tapAnimation)
setValue(_tapAnimation, "tapAnimation");
}
void CardsDisplaySettings::setArrowDrawAnimation(bool _arrowDrawAnimation)
{
setValue(_arrowDrawAnimation, "arrowDrawAnimation");
}
void CardsDisplaySettings::setAutoRotateSidewaysLayoutCards(bool _autoRotateSidewaysLayoutCards)
{
setValue(_autoRotateSidewaysLayoutCards, "autoRotateSidewaysLayoutCards");
@ -197,6 +216,15 @@ void CardsDisplaySettings::setVisualDeckEditorCardSize(int _cardSize)
emit visualDeckEditorCardSizeChanged();
}
void CardsDisplaySettings::setVisualDeckEditorShowCardCounts(bool _showCardCounts)
{
if (_showCardCounts == getVisualDeckEditorShowCardCounts()) {
return;
}
setValue(_showCardCounts, "visualDeckEditorShowCardCounts");
emit visualDeckEditorShowCardCountsChanged(_showCardCounts);
}
void CardsDisplaySettings::setEDHRecCardSize(int _edhrecCardSize)
{
setValue(_edhrecCardSize, "edhrec", "cards", "cardSize");
@ -214,3 +242,25 @@ void CardsDisplaySettings::setSampleHandSize(int _sampleHandSize)
setValue(_sampleHandSize, "sampleHandSize", "cards", "cardSize");
emit sampleHandSizeChanged(_sampleHandSize);
}
void CardsDisplaySettings::setCardLang(const QString &_cardLang)
{
if (_cardLang == getCardLang()) {
return;
}
setValue(_cardLang, "cardLang");
// Flush to disk immediately: the Oracle tool is a separate process that
// reads this value to decide which foreignData to import, so it must not
// observe a stale (pre-change) value.
sync();
emit cardLangChanged(_cardLang);
}
void CardsDisplaySettings::setCardSearchLanguage(int _cardSearchLanguage)
{
if (_cardSearchLanguage == getCardSearchLanguage()) {
return;
}
setValue(_cardSearchLanguage, "cardSearchLanguage");
emit cardSearchLanguageChanged(_cardSearchLanguage);
}

View file

@ -3,7 +3,11 @@
#include "settings_manager.h"
#include <QString>
#include <libcockatrice/interfaces/interface_cards_display_settings_provider.h>
#include <qtmetamacros.h>
class QObject;
class CardsDisplaySettings : public SettingsManager, public ICardsDisplaySettingsProvider
{
@ -17,9 +21,9 @@ public:
[[nodiscard]] bool getBumpSetsWithCardsInDeckToTop() const override;
[[nodiscard]] int getPrintingSelectorSortOrder() const override;
[[nodiscard]] int getPrintingSelectorCardSize() const override;
[[nodiscard]] bool getIncludeRebalancedCards() const override;
[[nodiscard]] bool getPrintingSelectorNavigationButtonsVisible() const override;
[[nodiscard]] bool getTapAnimation() const override;
[[nodiscard]] bool getArrowDrawAnimation() const override;
[[nodiscard]] bool getAutoRotateSidewaysLayoutCards() const override;
[[nodiscard]] bool getScaleCards() const override;
[[nodiscard]] int getStackCardOverlapPercent() const override;
@ -27,9 +31,12 @@ public:
[[nodiscard]] int getVisualDeckStorageCardSize() const override;
[[nodiscard]] int getVisualDatabaseDisplayCardSize() const override;
[[nodiscard]] int getVisualDeckEditorCardSize() const override;
[[nodiscard]] bool getVisualDeckEditorShowCardCounts() const override;
[[nodiscard]] int getEDHRecCardSize() const override;
[[nodiscard]] int getArchidektPreviewSize() const override;
[[nodiscard]] int getSampleHandSize() const override;
[[nodiscard]] QString getCardLang() const override;
[[nodiscard]] int getCardSearchLanguage() const override;
void setDisplayCardNames(bool _displayCardNames);
void setRoundCardCorners(bool _roundCardCorners);
@ -37,9 +44,9 @@ public:
void setBumpSetsWithCardsInDeckToTop(bool _bumpSetsWithCardsInDeckToTop);
void setPrintingSelectorSortOrder(int _printingSelectorSortOrder);
void setPrintingSelectorCardSize(int _printingSelectorCardSize);
void setIncludeRebalancedCards(bool _includeRebalancedCards);
void setPrintingSelectorNavigationButtonsVisible(bool _navigationButtonsVisible);
void setTapAnimation(bool _tapAnimation);
void setArrowDrawAnimation(bool _arrowDrawAnimation);
void setAutoRotateSidewaysLayoutCards(bool _autoRotateSidewaysLayoutCards);
void setCardScaling(bool _scaleCards);
void setStackCardOverlapPercent(int _verticalCardOverlapPercent);
@ -47,9 +54,12 @@ public:
void setVisualDeckStorageCardSize(int _cardSize);
void setVisualDatabaseDisplayCardSize(int _cardSize);
void setVisualDeckEditorCardSize(int _cardSize);
void setVisualDeckEditorShowCardCounts(bool _showCardCounts);
void setEDHRecCardSize(int _edhrecCardSize);
void setArchidektPreviewCardSize(int _archidektPreviewCardSize);
void setSampleHandSize(int _sampleHandSize);
void setCardLang(const QString &_cardLang);
void setCardSearchLanguage(int _cardSearchLanguage);
signals:
void displayCardNamesChanged();
@ -58,14 +68,16 @@ signals:
void bumpSetsWithCardsInDeckToTopChanged();
void printingSelectorSortOrderChanged();
void printingSelectorCardSizeChanged();
void includeRebalancedCardsChanged(bool _includeRebalancedCards);
void printingSelectorNavigationButtonsVisibleChanged();
void visualDeckStorageCardSizeChanged();
void visualDatabaseDisplayCardSizeChanged();
void visualDeckEditorCardSizeChanged();
void visualDeckEditorShowCardCountsChanged(bool showCardCounts);
void edhRecCardSizeChanged();
void archidektPreviewSizeChanged();
void sampleHandSizeChanged(int amount);
void cardLangChanged(const QString &lang);
void cardSearchLanguageChanged(int cardSearchLanguage);
public:
explicit CardsDisplaySettings(const QString &settingPath, QObject *parent = nullptr);

View file

@ -1,5 +1,11 @@
#include "chat_settings.h"
#include "libcockatrice/settings/settings_manager.h"
#include <QVariant>
class QObject;
ChatSettings::ChatSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "chat.ini", "chat", QString(), parent)
{
@ -65,6 +71,16 @@ bool ChatSettings::getRoomHistory() const
return getValue("roomHistory", QString(), QString(), true).toBool();
}
bool ChatSettings::getIgnoreAllPrivateMessages() const
{
return getValue("ignoreAllPrivateMessages", QString(), QString(), false).toBool();
}
bool ChatSettings::getUseGameTime() const
{
return getValue("useGameTime", QString(), QString(), false).toBool();
}
QString ChatSettings::getHighlightWords() const
{
return getValue("highlightWords").toString();
@ -131,6 +147,16 @@ void ChatSettings::setRoomHistory(bool _roomHistory)
setValue(_roomHistory, "roomHistory");
}
void ChatSettings::setIgnoreAllPrivateMessages(bool _ignoreAllPrivateMessages)
{
setValue(_ignoreAllPrivateMessages, "ignoreAllPrivateMessages");
}
void ChatSettings::setUseGameTime(bool _useGameTime)
{
setValue(_useGameTime, "useGameTime");
}
void ChatSettings::setHighlightWords(const QString &_highlightWords)
{
setValue(_highlightWords, "highlightWords");

View file

@ -3,7 +3,11 @@
#include "settings_manager.h"
#include <QString>
#include <libcockatrice/interfaces/interface_chat_settings_provider.h>
#include <qtmetamacros.h>
class QObject;
class ChatSettings : public SettingsManager, public IChatSettingsProvider
{
@ -23,6 +27,8 @@ public:
[[nodiscard]] bool getShowMessagePopup() const override;
[[nodiscard]] bool getShowMentionPopup() const override;
[[nodiscard]] bool getRoomHistory() const override;
[[nodiscard]] bool getIgnoreAllPrivateMessages() const override;
[[nodiscard]] bool getUseGameTime() const override;
[[nodiscard]] QString getHighlightWords() const override;
void setChatMention(bool _chatMention);
@ -37,6 +43,8 @@ public:
void setShowMessagePopups(bool _showMessagePopups);
void setShowMentionPopups(bool _showMentionPopups);
void setRoomHistory(bool _roomHistory);
void setIgnoreAllPrivateMessages(bool _ignoreAllPrivateMessages);
void setUseGameTime(bool _useGameTime);
void setHighlightWords(const QString &_highlightWords);
signals:

View file

@ -1,6 +1,16 @@
#include "commander_bracket_settings.h"
#include "libcockatrice/settings/settings_manager.h"
#include <QList>
#include <QMap>
#include <QSettings>
#include <QStringList>
#include <QVariant>
#include <QVariantMap>
#include <utility>
class QObject;
QVariantList CommanderBracketSettings::defaultDefinitions()
{

View file

@ -4,9 +4,11 @@
#include "settings_manager.h"
#include <QHash>
#include <QObject>
#include <QString>
#include <QVariantList>
#include <qtmetamacros.h>
class QObject;
struct CommanderBracketDefinition
{

View file

@ -1,6 +1,11 @@
#include "debug_settings.h"
#include <QtCore/QFile>
#include "libcockatrice/settings/settings_manager.h"
#include <QFile>
#include <QVariant>
class QObject;
DebugSettings::DebugSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "debug.ini", "debug", QString(), parent)

View file

@ -8,6 +8,11 @@
#define DEBUG_SETTINGS_H
#include "settings_manager.h"
#include <QString>
#include <qtmetamacros.h>
class QObject;
class DebugSettings : public SettingsManager
{
Q_OBJECT

View file

@ -1,5 +1,11 @@
#include "deck_editor_settings.h"
#include "libcockatrice/settings/settings_manager.h"
#include <QVariant>
class QObject;
DeckEditorSettings::DeckEditorSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "deck_editor.ini", "deckeditor", QString(), parent)
{
@ -25,6 +31,11 @@ int DeckEditorSettings::getDefaultDeckEditorType() const
return getValue("defaultDeckEditorType", QString(), QString(), 1).toInt();
}
int DeckEditorSettings::getVdeStartupTab() const
{
return getValue("vdeStartupTab", QString(), QString(), VdeStartupTabContext).toInt();
}
void DeckEditorSettings::setOpenDeckInNewTab(bool _openDeckInNewTab)
{
setValue(_openDeckInNewTab, "openDeckInNewTab");
@ -47,6 +58,12 @@ void DeckEditorSettings::setDefaultDeckEditorType(int _defaultDeckEditorType)
setValue(_defaultDeckEditorType, "defaultDeckEditorType");
}
void DeckEditorSettings::setVdeStartupTab(int _vdeStartupTab)
{
setValue(_vdeStartupTab, "vdeStartupTab");
emit vdeStartupTabChanged(_vdeStartupTab);
}
int DeckEditorSettings::getCommanderSpellbookIntegrationEnabled() const
{
return getValue("commanderspellbookintegrationenabled", QString(), QString(),

View file

@ -3,7 +3,11 @@
#include "settings_manager.h"
#include <QString>
#include <libcockatrice/interfaces/interface_deck_editor_settings_provider.h>
#include <qtmetamacros.h>
class QObject;
enum commanderSpellbookIntegrationEnabledIndex
{
@ -13,6 +17,13 @@ enum commanderSpellbookIntegrationEnabledIndex
commanderSpellbookIntegrationEnabledIndexUnprompted,
};
enum VdeStartupTab
{
VdeStartupTabContext, ///< Match the opened deck: new decks show the database display, existing decks the deck view
VdeStartupTabDeckDisplay, ///< Always open the Visual Deck View
VdeStartupTabDatabaseDisplay, ///< Always open the Visual Database Display
};
class DeckEditorSettings : public SettingsManager, public IDeckEditorSettingsProvider
{
Q_OBJECT
@ -23,6 +34,7 @@ public:
[[nodiscard]] bool getBannerCardComboBoxVisible() const override;
[[nodiscard]] bool getTagsWidgetVisible() const override;
[[nodiscard]] int getDefaultDeckEditorType() const override;
[[nodiscard]] int getVdeStartupTab() const;
[[nodiscard]] int getCommanderSpellbookIntegrationEnabled() const;
[[nodiscard]] bool getCommanderSpellbookIntegrationUseOfficialBracketNames() const;
@ -30,6 +42,7 @@ public:
void setBannerCardComboBoxVisible(bool _bannerCardComboBoxVisible);
void setTagsWidgetVisible(bool _tagsWidgetVisible);
void setDefaultDeckEditorType(int _defaultDeckEditorType);
void setVdeStartupTab(int _vdeStartupTab);
void setCommanderSpellbookIntegrationEnabled(int _commanderSpellbookIntegrationEnabled);
void setCommanderSpellbookIntegrationUseOfficialBracketNames(bool _useOfficialBracketNames);
@ -38,6 +51,7 @@ signals:
void tagsWidgetVisibleChanged(bool visible);
void commanderSpellbookIntegrationEnabledChanged(int enabled);
void commanderSpellbookIntegrationUseOfficialBracketNamesChanged(bool useOfficialBracketNames);
void vdeStartupTabChanged(int vdeStartupTab);
public:
explicit DeckEditorSettings(const QString &settingPath, QObject *parent = nullptr);

View file

@ -2,13 +2,40 @@
#include "settings_manager.h"
#include <QList>
#include <QSettings>
#include <QVariant>
#include <QtGlobal>
#include <utility>
class QObject;
const QStringList DownloadSettings::DEFAULT_DOWNLOAD_URLS = {
"https://cards.scryfall.io/large/!prop:side!/!set:uuid_substr_0_1!/!set:uuid_substr_1_1!/!set:uuid!.jpg",
"https://api.scryfall.com/cards/!set:uuid!?format=image&face=!prop:side!",
"https://api.scryfall.com/cards/multiverse/!set:muid!?format=image",
"https://api.scryfall.com/cards/!set:uuid!?format=image&face=!prop:side!&lang=!sflang!",
"https://api.scryfall.com/cards/multiverse/!set:muid!?format=image&lang=!sflang!",
"https://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!set:muid!&type=card",
"https://gatherer.wizards.com/Handlers/Image.ashx?name=!name!&type=card"};
const QString DownloadSettings::SCRYFALL_NAMED_LOCALIZED_URL =
"https://api.scryfall.com/cards/named?fuzzy=!localizedName!&lang=!sflang!&format=image&face=!prop:side!";
// Developer-set ceilings for the per-host request allowance. Users may lower a host's
// allowance via the download settings, but can never raise it above these values. Hosts
// not listed default to DEFAULT_HOST_REQUEST_LIMIT. A cap of UNLIMITED_HOST_QUOTA marks a
// host that is never throttled per host and skips the dispatch pacing (429 backoff still applies).
const QHash<QString, int> DownloadSettings::DEVELOPER_HOST_CAPS = {
// The Scryfall API enforces 10 requests/second; stay one under so a burst can't trip 429s.
{"api.scryfall.com", 9},
// The Scryfall image CDN has no documented per-client rate limit.
{"cards.scryfall.io", UNLIMITED_HOST_QUOTA},
};
const QHash<QString, int> &DownloadSettings::getDeveloperHostCaps()
{
return DEVELOPER_HOST_CAPS;
}
DownloadSettings::DownloadSettings(const QString &settingPath, QObject *parent = nullptr)
: SettingsManager(settingPath + "downloads.ini", "downloads", QString(), parent)
{
@ -29,6 +56,18 @@ void DownloadSettings::resetToDefaultURLs()
setValue(QVariant::fromValue(DEFAULT_DOWNLOAD_URLS), "urls");
}
bool DownloadSettings::addLocalizedScryfallUrl()
{
const QStringList urls = getAllURLs();
if (urls.contains(SCRYFALL_NAMED_LOCALIZED_URL)) {
return false;
}
QStringList updated = urls;
updated.prepend(SCRYFALL_NAMED_LOCALIZED_URL);
setDownloadUrls(updated);
return true;
}
bool DownloadSettings::getPicDownload() const
{
return getValue("pictureDownload", QString(), QString(), true).toBool();
@ -50,3 +89,57 @@ void DownloadSettings::setDownloadSpoilerStatus(bool _spoilerStatus)
setValue(_spoilerStatus, "downloadSpoilers");
emit downloadSpoilerStatusChanged();
}
QHash<QString, int> DownloadSettings::getHostRequestLimits() const
{
auto settings = getSettings();
if (!defaultGroup.isEmpty()) {
settings.beginGroup(defaultGroup);
}
settings.beginGroup("hostRequestLimits");
QHash<QString, int> hostRequestLimits;
const QStringList hosts = settings.childKeys();
for (const QString &host : hosts) {
hostRequestLimits.insert(host, settings.value(host).toInt());
}
settings.endGroup();
if (!defaultGroup.isEmpty()) {
settings.endGroup();
}
return hostRequestLimits;
}
void DownloadSettings::setHostRequestLimits(const QHash<QString, int> &hostRequestLimits)
{
auto settings = getSettings();
if (!defaultGroup.isEmpty()) {
settings.beginGroup(defaultGroup);
}
// Drop the legacy single-key form (an opaque @Variant blob) written by earlier builds so each
// host is stored as a plain, hand-editable key in its own subgroup.
settings.remove("hostRequestLimits");
settings.beginGroup("hostRequestLimits");
settings.remove(QString());
for (auto it = hostRequestLimits.cbegin(); it != hostRequestLimits.cend(); ++it) {
settings.setValue(it.key(), it.value());
}
settings.endGroup();
if (!defaultGroup.isEmpty()) {
settings.endGroup();
}
settings.sync();
emit hostRequestLimitsChanged();
}
int DownloadSettings::clampHostRequestLimit(const QString &host, int requested) const
{
const int devCap = DEVELOPER_HOST_CAPS.value(host, DEFAULT_HOST_REQUEST_LIMIT);
if (devCap == UNLIMITED_HOST_QUOTA) {
return qMax(MIN_HOST_REQUEST_LIMIT, requested);
}
return qBound(MIN_HOST_REQUEST_LIMIT, requested, devCap);
}

View file

@ -9,27 +9,77 @@
#include "settings_manager.h"
#include <QHash>
#include <QString>
#include <QStringList>
#include <qtmetamacros.h>
class QObject;
class DownloadSettings : public SettingsManager
{
Q_OBJECT
friend class SettingsCache;
static const QStringList DEFAULT_DOWNLOAD_URLS;
static const QString SCRYFALL_NAMED_LOCALIZED_URL;
static const QHash<QString, int> DEVELOPER_HOST_CAPS;
public:
/** @brief Per-host request allowance (requests/second) when no developer cap applies. */
static constexpr int DEFAULT_HOST_REQUEST_LIMIT = 10;
/** @brief Floor for any per-host request allowance. */
static constexpr int MIN_HOST_REQUEST_LIMIT = 1;
/**
* @brief Upper bound offered to the user when lowering an unlocked host's allowance.
*
* Unlocked hosts have no developer cap, so `clampHostRequestLimit` puts no upper bound on
* them; this only bounds what the settings dialog offers, and matches the widest paced
* allowance a user is documented to be able to hand-edit in `downloads.ini`. Choosing 0
* below this restores the "unlimited" fast path.
*/
static constexpr int UNLOCKED_HOST_LIMIT_MAX = 50;
/** @brief Developer cap marking a host as never throttled per host or by the dispatch pacing. */
static constexpr int UNLIMITED_HOST_QUOTA = -1;
/**
* @brief Developer-set per-host allowance ceilings (requests/second), keyed by host.
*
* Hosts not present default to DEFAULT_HOST_REQUEST_LIMIT. An entry of
* UNLIMITED_HOST_QUOTA marks a host that users may still lower, but that is never
* throttled per host by default. Users can never raise a host's allowance above its
* developer cap.
*/
static const QHash<QString, int> &getDeveloperHostCaps();
explicit DownloadSettings(const QString &, QObject *);
QStringList getAllURLs() const;
void setDownloadUrls(const QStringList &downloadURLs);
void resetToDefaultURLs();
[[nodiscard]] bool addLocalizedScryfallUrl();
[[nodiscard]] bool getPicDownload() const;
void setPicDownload(bool _picDownload);
[[nodiscard]] bool getDownloadSpoilersStatus() const;
void setDownloadSpoilerStatus(bool _spoilerStatus);
/** @brief User-set per-host request allowances (requests/second). Missing hosts use the developer default. */
QHash<QString, int> getHostRequestLimits() const;
void setHostRequestLimits(const QHash<QString, int> &hostRequestLimits);
/**
* @brief Clamps the user's requested allowance for a host against its developer cap.
* @param host The host to clamp for
* @param requested The user-requested allowance in requests/second
* @return The effective allowance. Users may lower a host's allowance but never raise it
* above the developer cap; hosts with UNLIMITED_HOST_QUOTA have no upper bound.
*/
[[nodiscard]] int clampHostRequestLimit(const QString &host, int requested) const;
signals:
void picDownloadChanged();
void downloadSpoilerStatusChanged();
void hostRequestLimitsChanged();
};
#endif // COCKATRICE_DOWNLOADSETTINGS_H

View file

@ -1,7 +1,15 @@
#include "game_filters_settings.h"
#include "libcockatrice/settings/settings_manager.h"
#include <QByteArray>
#include <QCryptographicHash>
#include <QList>
#include <QSharedPointer>
#include <QTime>
#include <QVariant>
class QObject;
GameFiltersSettings::GameFiltersSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "gamefilters.ini", "filter_games", QString(), parent)
@ -104,14 +112,14 @@ QString GameFiltersSettings::getGameNameFilter() const
return getValue("gameNameFilter").toString();
}
void GameFiltersSettings::setCreatorNameFilters(QStringList creatorName)
void GameFiltersSettings::setHostNameFilters(QStringList hostName)
{
setValue(creatorName, "creatorNameFilter");
setValue(hostName, "hostNameFilter");
}
QStringList GameFiltersSettings::getCreatorNameFilters() const
QStringList GameFiltersSettings::getHostNameFilters() const
{
return getValue("creatorNameFilter").toStringList();
return getValue("hostNameFilter").toStringList();
}
void GameFiltersSettings::setMinPlayers(int min)

View file

@ -10,6 +10,13 @@
#include "settings_manager.h"
#include <QString>
#include <QStringList>
#include <QTime>
#include <qtmetamacros.h>
class QObject;
class GameFiltersSettings : public SettingsManager
{
Q_OBJECT
@ -24,7 +31,7 @@ public:
bool isHideNotBuddyCreatedGames() const;
bool isHideOpenDecklistGames() const;
QString getGameNameFilter() const;
QStringList getCreatorNameFilters() const;
QStringList getHostNameFilters() const;
int getMinPlayers() const;
int getMaxPlayers() const;
QTime getMaxGameAge() const;
@ -42,7 +49,7 @@ public:
void setHidePasswordProtectedGames(bool hide);
void setHideNotBuddyCreatedGames(bool hide);
void setGameNameFilter(QString gameName);
void setCreatorNameFilters(QStringList creatorName);
void setHostNameFilters(QStringList hostName);
void setMinPlayers(int min);
void setMaxPlayers(int max);
void setMaxGameAge(const QTime &maxGameAge);

View file

@ -1,5 +1,11 @@
#include "game_settings.h"
#include "libcockatrice/settings/settings_manager.h"
#include <QVariant>
class QObject;
GameSettings::GameSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "game.ini", QString(), QString(), parent)
{

View file

@ -3,7 +3,11 @@
#include "settings_manager.h"
#include <QString>
#include <libcockatrice/interfaces/interface_game_settings_provider.h>
#include <qtmetamacros.h>
class QObject;
class GameSettings : public SettingsManager, public IGameSettingsProvider
{

View file

@ -1,7 +1,47 @@
#include "interface_settings.h"
#include "libcockatrice/settings/settings_manager.h"
#include "libcockatrice/utility/card_ref.h"
#include "libcockatrice/utility/playmat_params.h"
#include <QChar>
#include <QDir>
#include <QFileInfo>
#include <QList>
#include <QVariant>
#include <QtGlobal>
class QObject;
namespace
{
const QChar PLAYMAT_FIELD_SEP = QChar(0x1F); ///< Separator between PlaymatInfo fields.
QString encodePlaymatInfo(const PlaymatInfo &res)
{
return res.card.name + PLAYMAT_FIELD_SEP + res.card.providerId + PLAYMAT_FIELD_SEP +
QString::number(res.params.marginPctL, 'f', 4) + PLAYMAT_FIELD_SEP +
QString::number(res.params.marginPctR, 'f', 4) + PLAYMAT_FIELD_SEP +
QString::number(res.params.verticalOffset, 'f', 4) + PLAYMAT_FIELD_SEP +
QString::number(res.params.zoom, 'f', 4);
}
PlaymatInfo decodePlaymatInfo(const QString &encoded)
{
const QStringList fields = encoded.split(PLAYMAT_FIELD_SEP);
if (fields.size() != 6) {
return {};
}
PlaymatInfo res;
res.card.name = fields.at(0);
res.card.providerId = fields.at(1);
res.params.marginPctL = fields.at(2).toDouble();
res.params.marginPctR = fields.at(3).toDouble();
res.params.verticalOffset = fields.at(4).toDouble();
res.params.zoom = fields.at(5).toDouble();
return res;
}
} // namespace
InterfaceSettings::InterfaceSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "interface.ini", "interface", QString(), parent)
@ -10,7 +50,7 @@ InterfaceSettings::InterfaceSettings(const QString &settingPath, QObject *parent
bool InterfaceSettings::getUseTearOffMenus() const
{
return getValue("useTearOffMenus", QString(), QString(), true).toBool();
return getValue("useTearOffMenus", QString(), QString(), false).toBool();
}
int InterfaceSettings::getCardViewInitialRowsMax() const
@ -123,6 +163,11 @@ qreal InterfaceSettings::getFastForwardSpeed() const
return getValue("fastForwardSpeed", "replay", QString(), 10).toReal();
}
bool InterfaceSettings::getSkipEmptySections() const
{
return getValue("skipEmptySections", "replay", QString(), false).toBool();
}
bool InterfaceSettings::getLeftJustified() const
{
return getValue("leftJustified", QString(), QString(), false).toBool();
@ -171,6 +216,51 @@ bool InterfaceSettings::getTutorialCompleted() const
return getValue("tutorialCompleted", QString(), QString(), false).toBool();
}
int InterfaceSettings::getPlaymatVisibility() const
{
return qBound(0, getValue("playmatvisibility", QString(), QString(), 2).toInt(), 2);
}
QList<PlaymatInfo> InterfaceSettings::getPlaymatFallbackList() const
{
const QStringList entries = getValue("playmatFallbackList", QString(), QString(), QStringList()).toStringList();
QList<PlaymatInfo> result;
result.reserve(entries.size());
for (const QString &entry : entries) {
const PlaymatInfo res = decodePlaymatInfo(entry);
if (!res.card.isEmpty()) {
result.append(res);
}
}
return result;
}
int InterfaceSettings::getPlaymatMode() const
{
return qBound(0, getValue("playmatMode", QString(), QString(), 1).toInt(), 2);
}
int InterfaceSettings::getPlaymatFallbackBehavior() const
{
return qBound(0, getValue("playmatFallbackBehavior", QString(), QString(), 0).toInt(), 2);
}
bool InterfaceSettings::getLifeCounterAnimationsEnabled() const
{
return getValue("lifeCounterAnimationsEnabled", QString(), QString(), true).toBool();
}
bool InterfaceSettings::getBattlefieldFlashEnabled() const
{
return getValue("battlefieldFlashEnabled", QString(), QString(), true).toBool();
}
QStringList InterfaceSettings::getUserListExpandedSections() const
{
return getValue("userListExpandedSections", QString(), QString(), QStringList({"buddy", "online", "ignore"}))
.toStringList();
}
void InterfaceSettings::setUseTearOffMenus(bool _useTearOffMenus)
{
setValue(_useTearOffMenus, "useTearOffMenus");
@ -295,6 +385,11 @@ void InterfaceSettings::setFastForwardSpeed(qreal _value)
setValue(_value, "fastForwardSpeed", "replay");
}
void InterfaceSettings::setSkipEmptySections(bool _value)
{
setValue(_value, "skipEmptySections", "replay");
}
void InterfaceSettings::setLeftJustified(bool _leftJustified)
{
setValue(_leftJustified, "leftJustified");
@ -337,3 +432,60 @@ void InterfaceSettings::setTutorialCompleted(bool value)
{
setValue(value, "tutorialCompleted");
}
void InterfaceSettings::setPlaymatVisibility(int _visibility)
{
if (getPlaymatVisibility() == _visibility) {
return;
}
setValue(_visibility, "playmatvisibility");
emit playmatVisibilityChanged(_visibility);
}
void InterfaceSettings::setPlaymatFallbackList(const QList<PlaymatInfo> &_fallbackList)
{
QStringList entries;
entries.reserve(_fallbackList.size());
for (const PlaymatInfo &res : _fallbackList) {
entries.append(encodePlaymatInfo(res));
}
setValue(entries, "playmatFallbackList");
emit playmatSettingsChanged();
}
void InterfaceSettings::setPlaymatMode(int _mode)
{
const int mode = qBound(0, _mode, 2);
if (getPlaymatMode() == mode) {
return;
}
setValue(mode, "playmatMode");
emit playmatSettingsChanged();
}
void InterfaceSettings::setPlaymatFallbackBehavior(int _behavior)
{
const int behavior = qBound(0, _behavior, 2);
if (getPlaymatFallbackBehavior() == behavior) {
return;
}
setValue(behavior, "playmatFallbackBehavior");
emit playmatSettingsChanged();
}
void InterfaceSettings::setLifeCounterAnimationsEnabled(bool _lifeCounterAnimationsEnabled)
{
setValue(_lifeCounterAnimationsEnabled, "lifeCounterAnimationsEnabled");
emit lifeCounterAnimationsEnabledChanged(_lifeCounterAnimationsEnabled);
}
void InterfaceSettings::setBattlefieldFlashEnabled(bool _battlefieldFlashEnabled)
{
setValue(_battlefieldFlashEnabled, "battlefieldFlashEnabled");
emit battlefieldFlashEnabledChanged(_battlefieldFlashEnabled);
}
void InterfaceSettings::setUserListExpandedSections(const QStringList &sections)
{
setValue(sections, "userListExpandedSections");
}

View file

@ -3,7 +3,15 @@
#include "settings_manager.h"
#include <QString>
#include <QStringList>
#include <QtGlobal>
#include <libcockatrice/interfaces/interface_interface_settings_provider.h>
#include <qtmetamacros.h>
class QObject;
struct PlaymatInfo;
template <typename T> class QList;
class InterfaceSettings : public SettingsManager, public IInterfaceSettingsProvider
{
@ -34,6 +42,7 @@ public:
[[nodiscard]] int getMinPlayersForMultiColumnLayout() const override;
[[nodiscard]] int getRewindBufferingMs() const override;
[[nodiscard]] qreal getFastForwardSpeed() const override;
[[nodiscard]] bool getSkipEmptySections() const override;
[[nodiscard]] bool getLeftJustified() const override;
[[nodiscard]] int getZoneViewGroupByIndex() const override;
[[nodiscard]] int getZoneViewSortByIndex() const override;
@ -42,6 +51,13 @@ public:
[[nodiscard]] bool getShowShortcuts() const override;
[[nodiscard]] bool getShowGameSelectorFilterToolbar() const override;
[[nodiscard]] bool getTutorialCompleted() const;
[[nodiscard]] int getPlaymatVisibility() const override;
[[nodiscard]] QList<PlaymatInfo> getPlaymatFallbackList() const override;
[[nodiscard]] int getPlaymatMode() const override;
[[nodiscard]] int getPlaymatFallbackBehavior() const override;
[[nodiscard]] bool getLifeCounterAnimationsEnabled() const override;
[[nodiscard]] bool getBattlefieldFlashEnabled() const override;
[[nodiscard]] QStringList getUserListExpandedSections() const override;
void setUseTearOffMenus(bool _useTearOffMenus);
void setCardViewInitialRowsMax(int _cardViewInitialRowsMax);
@ -66,6 +82,7 @@ public:
void setMinPlayersForMultiColumnLayout(int _minPlayersForMultiColumnLayout);
void setRewindBufferingMs(int _rewindBufferingMs);
void setFastForwardSpeed(qreal _value);
void setSkipEmptySections(bool _value);
void setLeftJustified(bool _leftJustified);
void setZoneViewGroupByIndex(int _zoneViewGroupByIndex);
void setZoneViewSortByIndex(int _zoneViewSortByIndex);
@ -74,6 +91,13 @@ public:
void setShowShortcuts(bool _showShortcuts);
void setShowGameSelectorFilterToolbar(bool _showGameSelectorFilterToolbar);
void setTutorialCompleted(bool value);
void setPlaymatVisibility(int _visibility);
void setPlaymatFallbackList(const QList<PlaymatInfo> &_fallbackList);
void setPlaymatMode(int _mode);
void setPlaymatFallbackBehavior(int _behavior);
void setLifeCounterAnimationsEnabled(bool _lifeCounterAnimationsEnabled);
void setBattlefieldFlashEnabled(bool _battlefieldFlashEnabled);
void setUserListExpandedSections(const QStringList &sections);
signals:
void useTearOffMenusChanged(bool state);
@ -85,6 +109,10 @@ signals:
void tallyTypeChanged(int type);
void showStatusBarChanged(bool state);
void showGameSelectorFilterToolbarChanged(bool state);
void playmatVisibilityChanged(int visibility);
void playmatSettingsChanged();
void lifeCounterAnimationsEnabledChanged(bool state);
void battlefieldFlashEnabledChanged(bool state);
public:
explicit InterfaceSettings(const QString &settingPath, QObject *parent = nullptr);

View file

@ -1,5 +1,11 @@
#include "layouts_settings.h"
#include "libcockatrice/settings/settings_manager.h"
#include <QVariant>
class QObject;
const static QString STATE_PROP = "state";
const static QString GEOMETRY_PROP = "geometry";
const static QString SIZE_PROP = "widgetSize";

View file

@ -9,7 +9,11 @@
#include "settings_manager.h"
#include <QSize>
#include <QByteArray>
#include <QString>
#include <qtmetamacros.h>
class QObject;
class LayoutsSettings : public SettingsManager
{

View file

@ -1,5 +1,11 @@
#include "message_settings.h"
#include "libcockatrice/settings/settings_manager.h"
#include <QVariant>
class QObject;
MessageSettings::MessageSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "messages.ini", "messages", QString(), parent)
{

View file

@ -9,6 +9,11 @@
#include "settings_manager.h"
#include <QString>
#include <qtmetamacros.h>
class QObject;
class MessageSettings : public SettingsManager
{
Q_OBJECT

View file

@ -1,5 +1,11 @@
#include "network_settings.h"
#include "libcockatrice/settings/settings_manager.h"
#include <QVariant>
class QObject;
NetworkSettings::NetworkSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "network.ini", "network", QString(), parent)
{

View file

@ -9,6 +9,11 @@
#include "settings_manager.h"
#include <QString>
#include <qtmetamacros.h>
class QObject;
class NetworkSettings : public SettingsManager
{
Q_OBJECT

View file

@ -1,7 +1,10 @@
#include "paths_settings.h"
#include <QDir>
#include <QFile>
#include "libcockatrice/settings/settings_manager.h"
#include <QVariant>
class QObject;
PathsSettings::PathsSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "paths.ini", "paths", QString(), parent)

View file

@ -3,7 +3,11 @@
#include "settings_manager.h"
#include <QString>
#include <libcockatrice/interfaces/interface_paths_settings_provider.h>
#include <qtmetamacros.h>
class QObject;
class PathsSettings : public SettingsManager, public IPathsSettingsProvider
{

View file

@ -1,5 +1,12 @@
#include "personal_settings.h"
#include "libcockatrice/settings/settings_manager.h"
#include <QList>
#include <QVariant>
class QObject;
PersonalSettings::PersonalSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "personal.ini", "personal", QString(), parent)
{

View file

@ -3,9 +3,12 @@
#include "settings_manager.h"
#include <QDate>
#include <QList>
#include <QString>
#include <libcockatrice/interfaces/interface_personal_settings_provider.h>
#include <qtmetamacros.h>
class QObject;
template <typename T> class QList;
class PersonalSettings : public SettingsManager, public IPersonalSettingsProvider
{

View file

@ -1,5 +1,12 @@
#include "recents_settings.h"
#include "libcockatrice/settings/settings_manager.h"
#include <QList>
#include <QVariant>
class QObject;
#define MAX_RECENT_DECK_COUNT 10
RecentsSettings::RecentsSettings(const QString &settingPath, QObject *parent)

View file

@ -9,6 +9,12 @@
#include "settings_manager.h"
#include <QString>
#include <QStringList>
#include <qtmetamacros.h>
class QObject;
class RecentsSettings : public SettingsManager
{
Q_OBJECT

View file

@ -1,8 +1,17 @@
#include "servers_settings.h"
#include "libcockatrice/settings/settings_manager.h"
#include <QDebug>
#include <QList>
#include <QMessageLogger>
#include <QSharedPointer>
#include <QVariant>
#include <qnamespace.h>
#include <utility>
class QObject;
ServersSettings::ServersSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "servers.ini", "server", QString(), parent)
{
@ -171,7 +180,12 @@ void ServersSettings::addNewServer(const QString &saveName,
bool savePassword,
const QString &site)
{
if (updateExistingServer(saveName, serv, port, username, password, savePassword, site)) {
// Match the exact host-plus-port server the caller is adding, so a link or
// public-server list entry cannot clobber the port (and credentials) of an
// unrelated entry that happens to share the same hostname.
const int existingIndex = findServerIndex(serv, port);
if (existingIndex >= 0) {
updateServerFields(existingIndex, saveName, username, password, savePassword, site);
return;
}
@ -271,22 +285,7 @@ bool ServersSettings::updateExistingServer(QString saveName,
for (int i = 0; i <= size; ++i) {
if (serv == getValue(QString("server%1").arg(i), "server", "server_details").toString()) {
setValue(port, QString("port%1").arg(i), "server", "server_details");
if (!username.isEmpty()) {
setValue(username, QString("username%1").arg(i), "server", "server_details");
}
if (savePassword && !password.isEmpty()) {
setValue(password, QString("password%1").arg(i), "server", "server_details");
} else {
setValue(QString(), QString("password%1").arg(i), "server", "server_details");
}
if (!site.isEmpty()) {
setValue(site, QString("site%1").arg(i), "server", "server_details");
}
setValue(savePassword, QString("savePassword%1").arg(i), "server", "server_details");
setValue(saveName, QString("saveName%1").arg(i), "server", "server_details");
updateServerFields(i, saveName, username, password, savePassword, site);
return true;
}
@ -294,6 +293,31 @@ bool ServersSettings::updateExistingServer(QString saveName,
return false;
}
void ServersSettings::updateServerFields(int index,
const QString &saveName,
const QString &username,
const QString &password,
bool savePassword,
const QString &site)
{
if (!username.isEmpty()) {
setValue(username, QString("username%1").arg(index), "server", "server_details");
}
if (savePassword && !password.isEmpty()) {
setValue(password, QString("password%1").arg(index), "server", "server_details");
} else {
setValue(QString(), QString("password%1").arg(index), "server", "server_details");
}
if (!site.isEmpty()) {
setValue(site, QString("site%1").arg(index), "server", "server_details");
}
setValue(savePassword, QString("savePassword%1").arg(index), "server", "server_details");
setValue(saveName, QString("saveName%1").arg(index), "server", "server_details");
}
int ServersSettings::findServerIndex(const QString &host, const QString &port) const
{
int size = getValue("totalServers", "server", "server_details").toInt();
@ -310,6 +334,21 @@ int ServersSettings::findServerIndex(const QString &host, const QString &port) c
return -1;
}
int ServersSettings::findHostIndex(const QString &host) const
{
int size = getValue("totalServers", "server", "server_details").toInt();
for (int i = 0; i <= size; ++i) {
QString storedHost = getValue(QString("server%1").arg(i), "server", "server_details").toString();
if (storedHost.compare(host, Qt::CaseInsensitive) == 0) {
return i;
}
}
return -1;
}
bool ServersSettings::hasUsername(const QString &host, const QString &port) const
{
int index = findServerIndex(host, port);

View file

@ -10,7 +10,12 @@
#include "settings_manager.h"
#include <QLoggingCategory>
#include <QObject>
#include <QString>
#include <QStringList>
#include <qtmetamacros.h>
class QObject;
#define SERVERSETTINGS_DEFAULT_HOST "server.cockatrice.us"
#define SERVERSETTINGS_DEFAULT_PORT "4748"
@ -61,7 +66,14 @@ public:
QString password,
bool savePassword,
QString site = QString());
void updateServerFields(int index,
const QString &saveName,
const QString &username,
const QString &password,
bool savePassword,
const QString &site);
int findServerIndex(const QString &host, const QString &port) const;
int findHostIndex(const QString &host) const;
bool hasUsername(const QString &host, const QString &port) const;
bool hasCredentials(const QString &host, const QString &port) const;
bool hasLoginData(const QString &host, const QString &port) const;

View file

@ -7,9 +7,12 @@
#ifndef SETTINGSMANAGER_H
#define SETTINGSMANAGER_H
#include <QObject>
#include <QSettings>
#include <QStringList>
#include <QString>
#include <QVariant>
#include <functional>
#include <qtmetamacros.h>
class SettingsManager : public QObject
{

View file

@ -1,12 +1,54 @@
#include "settings_migration.h"
#include <QChar>
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QFileInfo>
#include <QFileInfoList>
#include <QFlags>
#include <QList>
#include <QLoggingCategory>
#include <QMap>
#include <QMessageLogger>
#include <QSettings>
#include <QSharedPointer>
#include <QStringList>
#include <QStringLiteral>
#include <QVariant>
#include <algorithm>
#include <utility>
inline Q_LOGGING_CATEGORY(SettingsMigrationLog, "settings_migration");
static const QString MIGRATION_SENTINEL_KEY = QStringLiteral("migration/perfile_complete");
/**
* True when global.ini holds keys that migrateSettingsFromGlobalIni() would still move, i.e. the
* exact condition that function uses to decide whether it has work to do. If a user runs an older
* Cockatrice build that writes to global.ini (non-sentinel keys) and then upgrades back, those new
* keys would otherwise be silently ignored, so they are re-migrated instead.
*/
static bool hasPendingGlobalIniSettings(const QString &settingsPath)
{
if (!QFile::exists(settingsPath + "global.ini")) {
return false;
}
QSettings globalIni(settingsPath + "global.ini", QSettings::IniFormat);
if (!globalIni.value(MIGRATION_SENTINEL_KEY, false).toBool()) {
return true;
}
// The sentinel is set, so any key besides it was written after the migration, i.e. by an
// older build sharing this file. Those are exactly the keys to re-migrate.
QStringList otherKeys = globalIni.allKeys();
otherKeys.removeAll(MIGRATION_SENTINEL_KEY);
return !otherKeys.isEmpty();
}
static void migrateTabsSettings(const QString &settingsPath, QSettings &globalIni)
{
if (!globalIni.contains("tabs/visualDeckStorage") && !globalIni.contains("tabs/server") &&
@ -217,7 +259,6 @@ static void migrateCardsDisplaySettings(const QString &settingsPath, QSettings &
{"cards/roundcardcorners", "cards/roundCardCorners"},
{"cards/overrideallcardartwithpersonalpreference", "cards/overrideAllCardArtWithPersonalPreference"},
{"cards/bumpsetswithcardsindecktotop", "cards/bumpSetsWithCardsInDeckToTop"},
{"cards/includerebalancedcards", "cards/includerebalancedcards"},
{"cards/tapanimation", "cards/tapAnimation"},
{"cards/autorotatesidewayslayoutcards", "cards/autoRotateSidewaysLayoutCards"},
{"cards/scaleCards", "cards/scaleCards"},
@ -374,7 +415,11 @@ static void migrateAppearanceSettings(const QString &settingsPath, QSettings &gl
QSettings appearanceIni(settingsPath + "appearance.ini", QSettings::IniFormat);
for (auto it = appearanceKeyMap.constBegin(); it != appearanceKeyMap.constEnd(); ++it) {
if (globalIni.contains(it.key())) {
appearanceIni.setValue(it.value(), globalIni.value(it.key()));
QVariant value = globalIni.value(it.key());
if (it.key() == "theme/name" && value.toString() == "Default") {
value = "System";
}
appearanceIni.setValue(it.value(), value);
}
}
}
@ -525,9 +570,15 @@ static void migrateLegacySets(const QString &settingsPath)
QSettings cardDbIni(settingsPath + "cardDatabase.ini", QSettings::IniFormat);
for (const auto &shortName : groups) {
legacySetting.beginGroup(shortName);
cardDbIni.setValue("sets/" + shortName + "/sortKey", legacySetting.value("sortkey"));
cardDbIni.setValue("sets/" + shortName + "/enabled", legacySetting.value("enabled"));
cardDbIni.setValue("sets/" + shortName + "/isKnown", legacySetting.value("isknown"));
if (legacySetting.contains("sortkey")) {
cardDbIni.setValue("sets/" + shortName + "/sortKey", legacySetting.value("sortkey"));
}
if (legacySetting.contains("enabled")) {
cardDbIni.setValue("sets/" + shortName + "/enabled", legacySetting.value("enabled"));
}
if (legacySetting.contains("isknown")) {
cardDbIni.setValue("sets/" + shortName + "/isKnown", legacySetting.value("isknown"));
}
legacySetting.endGroup();
}
legacySetting.endGroup();
@ -654,21 +705,11 @@ bool SettingsMigration::migrateLegacySettings(const QString &settingsPath)
bool SettingsMigration::migrateSettingsFromGlobalIni(const QString &settingsPath)
{
if (!QFile::exists(settingsPath + "global.ini")) {
if (!hasPendingGlobalIniSettings(settingsPath)) {
return false;
}
QSettings globalIni(settingsPath + "global.ini", QSettings::IniFormat);
if (globalIni.value(MIGRATION_SENTINEL_KEY, false).toBool()) {
// If a user runs an older Cockatrice build that writes to global.ini (non-sentinel keys)
// and then upgrades back, those new keys will be silently ignored. Re-migrate them.
globalIni.sync();
auto allKeys = globalIni.allKeys();
allKeys.removeAll(MIGRATION_SENTINEL_KEY);
if (allKeys.isEmpty()) {
return false;
}
}
migrateTabsSettings(settingsPath, globalIni);
migrateSoundSettings(settingsPath, globalIni);
@ -696,3 +737,78 @@ bool SettingsMigration::migrateSettingsFromGlobalIni(const QString &settingsPath
return true;
}
bool SettingsMigration::isMigrationPending(const QString &settingsPath)
{
if (hasPendingGlobalIniSettings(settingsPath)) {
return true;
}
QSettings personalIni(settingsPath + "personal.ini", QSettings::IniFormat);
if (personalIni.value("migration/legacy_complete", false).toBool()) {
return false;
}
// migrateLegacySettings() also runs on an empty profile, where it only writes its sentinel.
// Reporting that as pending would leave a backup folder behind on every fresh install, so
// only count it when there really are legacy keys left to move.
return !QSettings().allKeys().isEmpty();
}
QString SettingsMigration::backupSettingsDirectory(const QString &settingsPath)
{
QDir settingsDir(QDir::cleanPath(settingsPath));
if (!settingsDir.exists()) {
return QString();
}
const QDir::Filters fileFilters = QDir::Files | QDir::Hidden | QDir::System | QDir::NoDotAndDotDot;
// A profile holding nothing but empty files has nothing to lose, and backing it up would only
// leave clutter next to the settings folder.
const QFileInfoList files = settingsDir.entryInfoList(fileFilters);
const bool hasContent =
std::any_of(files.cbegin(), files.cend(), [](const QFileInfo &file) { return file.size() > 0; });
if (!hasContent) {
return QString();
}
// Keep the backup a sibling of the settings folder rather than a child: a folder that is never
// copied into its own backup, and one the "Open settings folder" action does not have to skip.
QDir backupParentDir(settingsDir.absolutePath());
if (!backupParentDir.cdUp()) {
qCWarning(SettingsMigrationLog) << "[SettingsMigration] Could not resolve the parent of the settings folder:"
<< settingsDir.absolutePath();
return QString();
}
const QString backupName =
QStringLiteral("settings-backup-") + QDateTime::currentDateTime().toString("yyyyMMdd-HHmmss");
QString backupPath = backupParentDir.absoluteFilePath(backupName);
// Two migrations inside the same second must not merge into a single folder.
for (int suffix = 1; QFileInfo::exists(backupPath); ++suffix) {
backupPath = backupParentDir.absoluteFilePath(backupName + '-' + QString::number(suffix));
}
if (!QDir().mkpath(backupPath)) {
qCWarning(SettingsMigrationLog) << "[SettingsMigration] Could not create the settings backup folder:"
<< backupPath;
return QString();
}
QDir backupDir(backupPath);
QDirIterator it(settingsDir.absolutePath(), fileFilters, QDirIterator::Subdirectories);
while (it.hasNext()) {
it.next();
const QString target = backupDir.absoluteFilePath(settingsDir.relativeFilePath(it.filePath()));
QDir().mkpath(QFileInfo(target).absolutePath());
if (!QFile::copy(it.filePath(), target)) {
// A partially copied backup still beats none: report it and let the migration run.
qCWarning(SettingsMigrationLog) << "[SettingsMigration] Could not back up settings file:" << it.filePath();
}
}
qCInfo(SettingsMigrationLog) << "[SettingsMigration] Settings backed up to" << backupPath;
return backupPath;
}

View file

@ -8,6 +8,19 @@ class SettingsMigration
public:
static bool migrateSettingsFromGlobalIni(const QString &settingsPath);
static bool migrateLegacySettings(const QString &settingsPath);
/**
* Returns true when at least one of the two migrations would still do work, i.e. when a
* backup of the settings directory is worth taking before they run.
*/
static bool isMigrationPending(const QString &settingsPath);
/**
* Copies the whole settings directory to a timestamped sibling folder, so a migration that
* overwrites per-domain files can be undone by hand. Returns the backup path, or an empty
* string when there was nothing to back up or the copy failed. Never aborts the migration.
*/
static QString backupSettingsDirectory(const QString &settingsPath);
};
#endif // SETTINGS_MIGRATION_H

View file

@ -1,5 +1,11 @@
#include "sound_settings.h"
#include "libcockatrice/settings/settings_manager.h"
#include <QVariant>
class QObject;
SoundSettings::SoundSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "sound.ini", "sound", QString(), parent)
{

View file

@ -3,7 +3,11 @@
#include "settings_manager.h"
#include <QString>
#include <libcockatrice/interfaces/interface_sound_settings_provider.h>
#include <qtmetamacros.h>
class QObject;
class SoundSettings : public SettingsManager, public ISoundSettingsProvider
{

View file

@ -1,10 +1,37 @@
#include "tabs_settings.h"
#include "libcockatrice/settings/settings_manager.h"
#include <QSharedPointer>
#include <QVariant>
class QObject;
TabsSettings::TabsSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "tabs.ini", "tabs", QString(), parent)
{
}
int TabsSettings::getStartupTabIndex() const
{
return getValue("startupTab", QString(), QString(), StartupTab::StartupTabHome).toInt();
}
QString TabsSettings::getStartupServerHost() const
{
return getValue("startupServerHost", QString(), QString(), QString()).toString();
}
QString TabsSettings::getStartupServerPort() const
{
return getValue("startupServerPort", QString(), QString(), QString()).toString();
}
QString TabsSettings::getStartupRoomName() const
{
return getValue("startupRoomName", QString(), QString(), QString()).toString();
}
bool TabsSettings::getTabVisualDeckStorageOpen() const
{
return getValue("visualDeckStorage", QString(), QString(), true).toBool();
@ -40,6 +67,57 @@ bool TabsSettings::getTabLogOpen() const
return getValue("log", QString(), QString(), true).toBool();
}
void TabsSettings::setStartupTabIndex(int value)
{
if (getStartupTabIndex() == value) {
return;
}
setValue(value, "startupTab");
emit startupTabIndexChanged(value);
}
void TabsSettings::setStartupServerHost(const QString &host)
{
if (getStartupServerHost() == host) {
return;
}
setValue(host, "startupServerHost");
emit startupServerHostChanged(host);
}
void TabsSettings::setStartupServerPort(const QString &port)
{
if (getStartupServerPort() == port) {
return;
}
setValue(port, "startupServerPort");
emit startupServerPortChanged(port);
}
void TabsSettings::setStartupRoomName(const QString &roomName)
{
if (getStartupRoomName() == roomName) {
return;
}
setValue(roomName, "startupRoomName");
emit startupRoomNameChanged(roomName);
}
bool TabsSettings::getTabReportOpen() const
{
return getValue("report", QString(), QString(), false).toBool();
}
bool TabsSettings::getTabModerationOpen() const
{
return getValue("moderation", QString(), QString(), false).toBool();
}
bool TabsSettings::getTabCardArtRulesOpen() const
{
return getValue("cardArtRules", QString(), QString(), false).toBool();
}
void TabsSettings::setTabVisualDeckStorageOpen(bool value)
{
setValue(value, "visualDeckStorage");
@ -74,3 +152,18 @@ void TabsSettings::setTabLogOpen(bool value)
{
setValue(value, "log");
}
void TabsSettings::setTabReportOpen(bool value)
{
setValue(value, "report");
}
void TabsSettings::setTabModerationOpen(bool value)
{
setValue(value, "moderation");
}
void TabsSettings::setTabCardArtRulesOpen(bool value)
{
setValue(value, "cardArtRules");
}

View file

@ -3,7 +3,30 @@
#include "settings_manager.h"
#include <QString>
#include <libcockatrice/interfaces/interface_tabs_settings_provider.h>
#include <qtmetamacros.h>
class QObject;
/**
* @brief The tab the application selects after launch.
*
* The destination is a request: tabs that were not open before (Deck Editor, Server Room, …)
* are opened as part of the startup flow. Destinations that require a server connection use the
* intent system to satisfy their pre-conditions.
*/
enum StartupTab
{
StartupTabHome, ///< The Home tab
StartupTabVisualDeckStorage, ///< The visual deck storage tab
StartupTabDeckStorage, ///< The deck storage tab
StartupTabReplays, ///< The game replays tab
StartupTabDeckEditor, ///< A fresh classic deck editor tab
StartupTabVisualDeckEditor, ///< A fresh visual deck editor tab
StartupTabServer, ///< The server lobby: connect and select the server tab
StartupTabServerRoom ///< A server room: connect and join the room by name
};
class TabsSettings : public SettingsManager, public ITabsSettingsProvider
{
@ -11,6 +34,10 @@ class TabsSettings : public SettingsManager, public ITabsSettingsProvider
friend class SettingsCache;
public:
[[nodiscard]] int getStartupTabIndex() const override;
[[nodiscard]] QString getStartupServerHost() const override;
[[nodiscard]] QString getStartupServerPort() const override;
[[nodiscard]] QString getStartupRoomName() const override;
[[nodiscard]] bool getTabVisualDeckStorageOpen() const override;
[[nodiscard]] bool getTabServerOpen() const override;
[[nodiscard]] bool getTabAccountOpen() const override;
@ -18,7 +45,14 @@ public:
[[nodiscard]] bool getTabReplaysOpen() const override;
[[nodiscard]] bool getTabAdminOpen() const override;
[[nodiscard]] bool getTabLogOpen() const override;
[[nodiscard]] bool getTabReportOpen() const override;
[[nodiscard]] bool getTabModerationOpen() const override;
[[nodiscard]] bool getTabCardArtRulesOpen() const override;
void setStartupTabIndex(int value);
void setStartupServerHost(const QString &host);
void setStartupServerPort(const QString &port);
void setStartupRoomName(const QString &roomName);
void setTabVisualDeckStorageOpen(bool value);
void setTabServerOpen(bool value);
void setTabAccountOpen(bool value);
@ -26,6 +60,15 @@ public:
void setTabReplaysOpen(bool value);
void setTabAdminOpen(bool value);
void setTabLogOpen(bool value);
void setTabReportOpen(bool value);
void setTabModerationOpen(bool value);
void setTabCardArtRulesOpen(bool value);
signals:
void startupTabIndexChanged(int index);
void startupServerHostChanged(const QString &host);
void startupServerPortChanged(const QString &port);
void startupRoomNameChanged(const QString &roomName);
public:
explicit TabsSettings(const QString &settingPath, QObject *parent = nullptr);

View file

@ -1,6 +1,13 @@
#include "updates_settings.h"
#include "libcockatrice/settings/settings_manager.h"
#include <QDate>
#include <QDateTime>
#include <QSharedPointer>
#include <QVariant>
class QObject;
UpdatesSettings::UpdatesSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "updates.ini", "updates", QString(), parent)
@ -53,6 +60,11 @@ bool UpdatesSettings::getNotifyAboutNewVersion() const
return getValue("newVersionNotification", QString(), QString(), true).toBool();
}
bool UpdatesSettings::getCardDatabaseUpdateTrayNotification() const
{
return getValue("cardDatabaseUpdateTrayNotification", QString(), QString(), true).toBool();
}
int UpdatesSettings::getUpdateReleaseChannelIndex() const
{
return getValue("updateReleaseChannel", QString(), QString(), 0).toInt();
@ -98,6 +110,11 @@ void UpdatesSettings::setNotifyAboutNewVersion(bool _notifyaboutnewversion)
setValue(_notifyaboutnewversion, "newVersionNotification");
}
void UpdatesSettings::setCardDatabaseUpdateTrayNotification(bool value)
{
setValue(value, "cardDatabaseUpdateTrayNotification");
}
void UpdatesSettings::setUpdateReleaseChannelIndex(int value)
{
setValue(value, "updateReleaseChannel");

View file

@ -3,8 +3,12 @@
#include "settings_manager.h"
#include <QDate>
#include <QString>
#include <libcockatrice/interfaces/interface_updates_settings_provider.h>
#include <qtmetamacros.h>
class QDate;
class QObject;
class UpdatesSettings : public SettingsManager, public IUpdatesSettingsProvider
{
@ -21,6 +25,7 @@ public:
[[nodiscard]] bool getAlwaysEnableNewSets() const override;
[[nodiscard]] bool getNotifyAboutUpdates() const override;
[[nodiscard]] bool getNotifyAboutNewVersion() const override;
[[nodiscard]] bool getCardDatabaseUpdateTrayNotification() const;
[[nodiscard]] int getUpdateReleaseChannelIndex() const override;
void setCheckUpdatesOnStartup(bool value);
@ -31,6 +36,7 @@ public:
void setAlwaysEnableNewSets(bool value);
void setNotifyAboutUpdates(bool _notifyaboutupdate);
void setNotifyAboutNewVersion(bool _notifyaboutnewversion);
void setCardDatabaseUpdateTrayNotification(bool value);
void setUpdateReleaseChannelIndex(int value);
public:

View file

@ -1,5 +1,12 @@
#include "visual_deck_storage_settings.h"
#include "libcockatrice/settings/settings_manager.h"
#include <QList>
#include <QVariant>
class QObject;
namespace
{
QStringList defaultTags = {
@ -130,6 +137,11 @@ bool VisualDeckStorageSettings::getVisualDeckStorageShowTagsOnDeckPreviews() con
return getValue("showTagsOnDeckPreviews", "interface", "visualDeckStorage", true).toBool();
}
bool VisualDeckStorageSettings::getVisualDeckStorageShowUploadTime() const
{
return getValue("showUploadTime", "interface", "visualDeckStorage", true).toBool();
}
bool VisualDeckStorageSettings::getVisualDeckStorageDrawUnusedColorIdentities() const
{
return getValue("drawUnusedColorIdentities", "interface", "visualDeckStorage", true).toBool();
@ -220,6 +232,12 @@ void VisualDeckStorageSettings::setVisualDeckStorageShowTagsOnDeckPreviews(bool
emit visualDeckStorageShowTagsOnDeckPreviewsChanged(_showTags);
}
void VisualDeckStorageSettings::setVisualDeckStorageShowUploadTime(bool value)
{
setValue(value, "showUploadTime", "interface", "visualDeckStorage");
emit visualDeckStorageShowUploadTimeChanged(value);
}
void VisualDeckStorageSettings::setVisualDeckStorageDrawUnusedColorIdentities(bool _draw)
{
setValue(_draw, "drawUnusedColorIdentities", "interface", "visualDeckStorage");

View file

@ -3,8 +3,12 @@
#include "settings_manager.h"
#include <QString>
#include <QStringList>
#include <libcockatrice/interfaces/interface_visual_deck_storage_settings_provider.h>
#include <qtmetamacros.h>
class QObject;
class VisualDeckStorageSettings : public SettingsManager, public IVisualDeckStorageSettingsProvider
{
@ -20,6 +24,7 @@ public:
[[nodiscard]] bool getVisualDeckStorageShowColorIdentity() const override;
[[nodiscard]] bool getVisualDeckStorageShowBannerCardComboBox() const override;
[[nodiscard]] bool getVisualDeckStorageShowTagsOnDeckPreviews() const override;
[[nodiscard]] bool getVisualDeckStorageShowUploadTime() const;
[[nodiscard]] bool getVisualDeckStorageDrawUnusedColorIdentities() const override;
[[nodiscard]] int getVisualDeckStorageUnusedColorIdentitiesOpacity() const override;
[[nodiscard]] int getVisualDeckStorageTooltipType() const override;
@ -38,6 +43,7 @@ public:
void setVisualDeckStorageShowColorIdentity(bool value);
void setVisualDeckStorageShowBannerCardComboBox(bool _showBannerCardComboBox);
void setVisualDeckStorageShowTagsOnDeckPreviews(bool _showTags);
void setVisualDeckStorageShowUploadTime(bool value);
void setVisualDeckStorageDrawUnusedColorIdentities(bool _draw);
void setVisualDeckStorageUnusedColorIdentitiesOpacity(int _opacity);
void setVisualDeckStorageTooltipType(int value);
@ -54,6 +60,7 @@ signals:
void visualDeckStorageShowColorIdentityChanged(bool _visible);
void visualDeckStorageShowBannerCardComboBoxChanged(bool _visible);
void visualDeckStorageShowTagsOnDeckPreviewsChanged(bool _visible);
void visualDeckStorageShowUploadTimeChanged(bool _visible);
void visualDeckStorageDrawUnusedColorIdentitiesChanged(bool _visible);
void visualDeckStorageUnusedColorIdentitiesOpacityChanged(bool value);
void visualDeckStorageInGameChanged(bool enabled);