[Fonts] Retire max font size setting into theme CardText min/max range

The per-card text size lives in the theme now, so a theme can pick the size
it was designed for instead of every user overriding it globally.

- CardText stores family + max point size; CardTextMinSize stores the
  overlay shrink-to-fit floor (omitted from fonts.toml when 1).
- ThemeManager::cardTextMaxSize()/cardTextMinSize() resolve the range with
  the retired defaults (12 max, 1 min), and cardTextMaxSize() falls back to
  the CardText role size that #7541 made live.
- The overlay widget starts at the explicit size when one is set (deck
  preview), else the theme max, and shrinks down to the theme floor.
- The appearance page's "Maximum font size" spin is gone; the range is
  edited in the theme's font grid, whose min/max spins now keep the saved
  range consistent with each other.
- Migration: appearance/maxFontSize is seeded into every user theme folder
  and into the active theme's folder (created when the theme is a shipped
  one that only exists in the system directory). The retired
  game/maxfontsize -> appearance/maxFontSize mapping stays, so users still
  on global.ini keep their value through the chain.
This commit is contained in:
Lukas Brübach 2026-10-16 11:27:31 +02:00
parent 69f4fb02db
commit 8c2bfc46ff
19 changed files with 697 additions and 49 deletions

View file

@ -137,6 +137,9 @@ SettingsCache::SettingsCache()
// before that rename (idempotent, safe to run on every startup, portable builds included). // before that rename (idempotent, safe to run on every startup, portable builds included).
SettingsMigration::migrateGameFiltersToCamelCase(settingsPath); SettingsMigration::migrateGameFiltersToCamelCase(settingsPath);
// Retire appearance/maxFontSize into per-theme CardText max (idempotent, safe every startup).
SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath);
shortcutsSettings = new ShortcutsSettings(settingsPath, this); shortcutsSettings = new ShortcutsSettings(settingsPath, this);
cardDatabaseSettings = new CardDatabaseSettings(settingsPath, this); cardDatabaseSettings = new CardDatabaseSettings(settingsPath, this);
serversSettings = new ServersSettings(settingsPath, this); serversSettings = new ServersSettings(settingsPath, this);

View file

@ -35,7 +35,6 @@
#include <QVariant> #include <QVariant>
#include <QtGlobal> #include <QtGlobal>
#include <algorithm> #include <algorithm>
#include <libcockatrice/settings/appearance_settings.h>
#include <libcockatrice/settings/cards_display_settings.h> #include <libcockatrice/settings/cards_display_settings.h>
#include <libcockatrice/settings/debug_settings.h> #include <libcockatrice/settings/debug_settings.h>
#include <qnamespace.h> #include <qnamespace.h>
@ -144,8 +143,8 @@ QSizeF AbstractCardItem::getTranslatedSize(QPainter *painter) const
void AbstractCardItem::transformPainter(QPainter *painter, const QSizeF &translatedSize, int angle) void AbstractCardItem::transformPainter(QPainter *painter, const QSizeF &translatedSize, int angle)
{ {
const int MAX_FONT_SIZE = SettingsCache::instance().appearance().getMaxFontSize(); const int minSize = std::max(1, themeManager->cardTextMinSize());
const int fontSize = std::max(9, MAX_FONT_SIZE); const int fontSize = std::max(minSize, std::max(1, themeManager->cardTextMaxSize()));
QRectF totalBoundingRect = painter->combinedTransform().mapRect(boundingRect()); QRectF totalBoundingRect = painter->combinedTransform().mapRect(boundingRect());

View file

@ -13,6 +13,8 @@
static constexpr int MIN_FONT_SIZE = 6; static constexpr int MIN_FONT_SIZE = 6;
static constexpr int MAX_FONT_SIZE = 72; static constexpr int MAX_FONT_SIZE = 72;
static constexpr int CARD_TEXT_MIN_SIZE = 1;
static constexpr int CARD_TEXT_MAX_SIZE = 12;
static constexpr int FALLBACK_FONT_SIZE = 10; static constexpr int FALLBACK_FONT_SIZE = 10;
static const QMap<AppFont::Role, const char *> APP_FONT_DESCRIPTIONS = { static const QMap<AppFont::Role, const char *> APP_FONT_DESCRIPTIONS = {
@ -46,6 +48,7 @@ FontGridWidget::FontGridWidget(QWidget *parent) : QWidget(parent)
grid->setColumnStretch(0, 0); grid->setColumnStretch(0, 0);
grid->setColumnStretch(1, 1); grid->setColumnStretch(1, 1);
grid->setColumnStretch(2, 0); grid->setColumnStretch(2, 0);
grid->setColumnStretch(3, 0);
QMetaEnum fontEnum = QMetaEnum::fromType<AppFont::Role>(); QMetaEnum fontEnum = QMetaEnum::fromType<AppFont::Role>();
for (int i = 0; i < fontEnum.keyCount(); ++i) { for (int i = 0; i < fontEnum.keyCount(); ++i) {
@ -58,6 +61,30 @@ FontGridWidget::FontGridWidget(QWidget *parent) : QWidget(parent)
grid->addWidget(familyCombo, i, 1); grid->addWidget(familyCombo, i, 1);
connect(familyCombo, &QFontComboBox::currentFontChanged, this, [this] { emit fontsChanged(); }); connect(familyCombo, &QFontComboBox::currentFontChanged, this, [this] { emit fontsChanged(); });
if (role == AppFont::CardText) {
auto *minSpin = new QSpinBox(this);
minSpin->setRange(CARD_TEXT_MIN_SIZE, MAX_FONT_SIZE);
grid->addWidget(minSpin, i, 2);
connect(minSpin, &QSpinBox::valueChanged, this, [this] { emit fontsChanged(); });
auto *maxSpin = new QSpinBox(this);
maxSpin->setRange(MIN_FONT_SIZE, MAX_FONT_SIZE);
grid->addWidget(maxSpin, i, 3);
connect(maxSpin, &QSpinBox::valueChanged, this, [this] { emit fontsChanged(); });
// Keep the range consistent: otherwise min 20 / max 12 saves as-is
// and cardTextMaxSize() silently clamps the max up to the min, so
// the max spin shows a size that is never used. Moving one spin
// pulls the other's bound along.
connect(minSpin, &QSpinBox::valueChanged, this, [maxSpin](int value) { maxSpin->setMinimum(value); });
connect(maxSpin, &QSpinBox::valueChanged, this, [minSpin](int value) { minSpin->setMaximum(value); });
label->setBuddy(familyCombo);
fontRows[role] = {label, familyCombo, maxSpin, minSpin};
continue;
}
auto *sizeSpin = new QSpinBox(this); auto *sizeSpin = new QSpinBox(this);
sizeSpin->setRange(MIN_FONT_SIZE, MAX_FONT_SIZE); sizeSpin->setRange(MIN_FONT_SIZE, MAX_FONT_SIZE);
grid->addWidget(sizeSpin, i, 2); grid->addWidget(sizeSpin, i, 2);
@ -65,7 +92,7 @@ FontGridWidget::FontGridWidget(QWidget *parent) : QWidget(parent)
label->setBuddy(familyCombo); label->setBuddy(familyCombo);
fontRows[role] = {label, familyCombo, sizeSpin}; fontRows[role] = {label, familyCombo, sizeSpin, nullptr};
} }
retranslateUi(); retranslateUi();
@ -89,6 +116,37 @@ void FontGridWidget::loadFonts(const FontConfig &cfg)
const QFont font = cfg.fonts.value(role, fallback); const QFont font = cfg.fonts.value(role, fallback);
row.familyCombo->setCurrentFont(font); row.familyCombo->setCurrentFont(font);
if (role == AppFont::CardText && row.minSpin != nullptr) {
// A previous load may have narrowed the linked bounds (min 20 pulls
// max up to 20), which would clamp the values written below. Reset
// both to the full range first; the valueChanged lambdas re-link
// them as the values are set.
row.minSpin->setRange(CARD_TEXT_MIN_SIZE, MAX_FONT_SIZE);
row.sizeSpin->setRange(MIN_FONT_SIZE, MAX_FONT_SIZE);
// The max spin is a size-only override. Without CardTextMaxSize it
// follows the CardText family size, and without CardText at all it
// must match cardTextMaxSize()'s default rather than the system
// startup size, which currentFontConfig() would otherwise bake in.
const int fallbackMax = cfg.fonts.contains(role) ? font.pointSize() : CARD_TEXT_MAX_SIZE;
int maxSize = cfg.cardTextMaxSize > 0 ? cfg.cardTextMaxSize : fallbackMax;
if (maxSize <= 0) {
maxSize = CARD_TEXT_MAX_SIZE;
}
int minSize = cfg.cardTextMinSize > 0 ? cfg.cardTextMinSize : themeManager->cardTextMinSize();
minSize = qBound(CARD_TEXT_MIN_SIZE, minSize, MAX_FONT_SIZE);
maxSize = qBound(MIN_FONT_SIZE, maxSize, MAX_FONT_SIZE);
if (maxSize < minSize) {
maxSize = minSize;
}
// Max first, then min: each value re-applies the linked bound, so
// the pair ends up consistent instead of clamping the smaller one.
row.sizeSpin->setValue(maxSize);
row.minSpin->setValue(minSize);
continue;
}
int size = font.pointSize(); int size = font.pointSize();
if (size <= 0) { if (size <= 0) {
size = qApp->font().pointSize(); size = qApp->font().pointSize();
@ -114,6 +172,11 @@ FontConfig FontGridWidget::currentFontConfig() const
QFont font(row.familyCombo->currentFont().family()); QFont font(row.familyCombo->currentFont().family());
font.setPointSize(row.sizeSpin->value()); font.setPointSize(row.sizeSpin->value());
cfg.fonts[role] = font; cfg.fonts[role] = font;
if (role == AppFont::CardText && row.minSpin != nullptr) {
cfg.cardTextMinSize = row.minSpin->value();
cfg.cardTextMaxSize = row.sizeSpin->value();
}
} }
return cfg; return cfg;
} }
@ -129,6 +192,15 @@ void FontGridWidget::retranslateUi()
row.label->setToolTip(tr(APP_FONT_DESCRIPTIONS.value(role, ""))); row.label->setToolTip(tr(APP_FONT_DESCRIPTIONS.value(role, "")));
row.familyCombo->setToolTip(tr(APP_FONT_DESCRIPTIONS.value(role, ""))); row.familyCombo->setToolTip(tr(APP_FONT_DESCRIPTIONS.value(role, "")));
row.familyCombo->setAccessibleName(roleDisplayName(role)); row.familyCombo->setAccessibleName(roleDisplayName(role));
if (role == AppFont::CardText && row.minSpin != nullptr) {
row.minSpin->setToolTip(tr("Minimum card text size"));
row.minSpin->setAccessibleName(tr("Minimum card text size"));
row.minSpin->setSuffix(tr(" pt"));
row.sizeSpin->setToolTip(tr("Maximum card text size"));
row.sizeSpin->setAccessibleName(tr("Maximum card text size"));
row.sizeSpin->setSuffix(tr(" pt"));
continue;
}
row.sizeSpin->setToolTip(tr("Point size for %1").arg(roleDisplayName(role))); row.sizeSpin->setToolTip(tr("Point size for %1").arg(roleDisplayName(role)));
row.sizeSpin->setAccessibleName(tr("Point size for %1").arg(roleDisplayName(role))); row.sizeSpin->setAccessibleName(tr("Point size for %1").arg(roleDisplayName(role)));
row.sizeSpin->setSuffix(tr(" pt")); row.sizeSpin->setSuffix(tr(" pt"));

View file

@ -35,6 +35,7 @@ private:
QLabel *label = nullptr; QLabel *label = nullptr;
QFontComboBox *familyCombo = nullptr; QFontComboBox *familyCombo = nullptr;
QSpinBox *sizeSpin = nullptr; QSpinBox *sizeSpin = nullptr;
QSpinBox *minSpin = nullptr; // CardText only: shrink-to-fit floor
}; };
QMap<AppFont::Role, FontRow> fontRows; QMap<AppFont::Role, FontRow> fontRows;

View file

@ -308,7 +308,7 @@ QPalette PaletteConfig::apply(QPalette base) const
bool FontConfig::hasFonts() const bool FontConfig::hasFonts() const
{ {
return !fonts.isEmpty(); return !fonts.isEmpty() || cardTextMinSize != 1 || cardTextMaxSize > 0;
} }
QString FontConfig::toToml() const QString FontConfig::toToml() const
@ -334,6 +334,14 @@ QString FontConfig::toToml() const
entries += QString("%1 = \"%2, %3\"\n").arg(QString(roleName), -20).arg(family).arg(pointSize); entries += QString("%1 = \"%2, %3\"\n").arg(QString(roleName), -20).arg(family).arg(pointSize);
} }
if (cardTextMinSize != 1) {
entries += QString("%1 = %2\n").arg(QStringLiteral("CardTextMinSize"), -20).arg(cardTextMinSize);
}
if (cardTextMaxSize > 0) {
entries += QString("%1 = %2\n").arg(QStringLiteral("CardTextMaxSize"), -20).arg(cardTextMaxSize);
}
if (entries.isEmpty()) { if (entries.isEmpty()) {
return {}; return {};
} }
@ -401,6 +409,31 @@ FontConfig FontConfig::fromFile(const QString &filePath)
value = value.mid(1, value.size() - 2).trimmed(); value = value.mid(1, value.size() - 2).trimmed();
} }
// CardText size bounds: bare integers, e.g. CardTextMinSize = 2.
// Tolerate quoting like the family entries; skip invalid lines.
QString sizeKey = key;
if (sizeKey.startsWith("AppFont::")) {
sizeKey = sizeKey.mid(QStringLiteral("AppFont::").size());
}
if (sizeKey.compare("CardTextMinSize", Qt::CaseInsensitive) == 0) {
bool ok = false;
int minSize = value.trimmed().toInt(&ok);
if (!ok || minSize <= 0) {
continue;
}
cfg.cardTextMinSize = minSize;
continue;
}
if (sizeKey.compare("CardTextMaxSize", Qt::CaseInsensitive) == 0) {
bool ok = false;
int maxSize = value.trimmed().toInt(&ok);
if (!ok || maxSize <= 0) {
continue;
}
cfg.cardTextMaxSize = maxSize;
continue;
}
int comma = value.lastIndexOf(','); int comma = value.lastIndexOf(',');
if (comma < 0) { if (comma < 0) {

View file

@ -84,15 +84,23 @@ struct PaletteConfig
* @code * @code
* [Fonts] * [Fonts]
* InterfaceDefault = "Inter, 10" * InterfaceDefault = "Inter, 10"
* CardText = "Noto Serif, 12"
* CardTextMinSize = 1
* @endcode * @endcode
* CardText stores family + MAX size; cardTextMinSize stores the overlay
* shrink-to-fit floor (omitted from TOML when 1).
*/ */
struct FontConfig struct FontConfig
{ {
QMap<AppFont::Role, QFont> fonts; QMap<AppFont::Role, QFont> fonts;
int cardTextMinSize = 1;
/// Optional size-only maximum for CardText, used by the retired maxFontSize
/// migration so seeding it does not have to pin a family.
int cardTextMaxSize = 0;
bool operator==(const FontConfig &rhs) const bool operator==(const FontConfig &rhs) const
{ {
return fonts == rhs.fonts; return fonts == rhs.fonts && cardTextMinSize == rhs.cardTextMinSize && cardTextMaxSize == rhs.cardTextMaxSize;
} }
bool operator!=(const FontConfig &rhs) const bool operator!=(const FontConfig &rhs) const
{ {

View file

@ -681,6 +681,7 @@ QFont ThemeManager::cardTextFont() const
} }
QFont serifFont(QStringLiteral("Serif")); QFont serifFont(QStringLiteral("Serif"));
serifFont.setStyleHint(QFont::Serif); serifFont.setStyleHint(QFont::Serif);
serifFont.setPointSize(12);
return serifFont; return serifFont;
} }
@ -702,6 +703,29 @@ QFont ThemeManager::defaultAppFont(AppFont::Role /*role*/) const
return defaultFont; return defaultFont;
} }
int ThemeManager::cardTextMaxSize() const
{
if (currentFonts.cardTextMaxSize > 0) {
return currentFonts.cardTextMaxSize;
}
const auto it = currentFonts.fonts.constFind(AppFont::CardText);
if (it != currentFonts.fonts.constEnd()) {
const int size = it.value().pointSize();
if (size > 0) {
return size;
}
}
return 12;
}
int ThemeManager::cardTextMinSize() const
{
if (currentFonts.cardTextMinSize > 0) {
return currentFonts.cardTextMinSize;
}
return 1;
}
void ThemeManager::themeChangedSlot() void ThemeManager::themeChangedSlot()
{ {
QString themeName = SettingsCache::instance().getThemeName(); QString themeName = SettingsCache::instance().getThemeName();

View file

@ -169,6 +169,11 @@ public:
* "what this role resolves to" must use this, because appFont() * "what this role resolves to" must use this, because appFont()
* reflects a live preview while the editor is open. */ * reflects a live preview while the editor is open. */
QFont defaultAppFont(AppFont::Role role) const; QFont defaultAppFont(AppFont::Role role) const;
/** @brief CardText max size: stored CardTextMaxSize, else the CardText
* point size, else 12. */
int cardTextMaxSize() const;
/** @brief CardText shrink-to-fit floor: stored CardTextMinSize, else 1. */
int cardTextMinSize() const;
QBrush &getBgBrush(Role zone); QBrush &getBgBrush(Role zone);
QBrush getExtraBgBrush(Role zone, int zoneId = 0); QBrush getExtraBgBrush(Role zone, int zoneId = 0);

View file

@ -87,6 +87,7 @@ void CardInfoPictureWithTextOverlayWidget::setOutlineColor(const QColor &color)
void CardInfoPictureWithTextOverlayWidget::setFontSize(const int size) void CardInfoPictureWithTextOverlayWidget::setFontSize(const int size)
{ {
fontSize = size > 0 ? size : 1; fontSize = size > 0 ? size : 1;
hasExplicitFontSize = true;
update(); update();
} }
@ -179,10 +180,12 @@ void CardInfoPictureWithTextOverlayWidget::paintEvent(QPaintEvent *event)
} }
// Calculate the optimal font size. The family comes from the CardText // Calculate the optimal font size. The family comes from the CardText
// theme role; the fontSize member stays the starting size and the // theme role; the start size is the explicit size when set (e.g. deck
// shrink-to-fit logic below is unchanged. // preview 24), else the theme max, shrinking down to the theme min floor.
QFont font = themeManager->appFont(AppFont::CardText); QFont font = themeManager->appFont(AppFont::CardText);
int optimalFontSize = fontSize; // Start with the user-defined font size const int themeMin = std::max(1, themeManager->cardTextMinSize());
const int themeMax = std::max(themeMin, std::max(1, themeManager->cardTextMaxSize()));
int optimalFontSize = hasExplicitFontSize ? std::max(1, fontSize) : themeMax;
const QFontMetrics baseMetrics(font); const QFontMetrics baseMetrics(font);
int textWidth = pixmapRect.width(); int textWidth = pixmapRect.width();
@ -200,7 +203,7 @@ void CardInfoPictureWithTextOverlayWidget::paintEvent(QPaintEvent *event)
} }
--optimalFontSize; --optimalFontSize;
} while (optimalFontSize > 1); } while (optimalFontSize > themeMin);
// Apply the calculated font size // Apply the calculated font size
painter.setFont(font); painter.setFont(font);
@ -227,7 +230,7 @@ void CardInfoPictureWithTextOverlayWidget::paintEvent(QPaintEvent *event)
int totalTextHeight = wrappedText.count('\n') * fontMetrics.height() + fontMetrics.height(); int totalTextHeight = wrappedText.count('\n') * fontMetrics.height() + fontMetrics.height();
// Adjust font size if the total text height exceeds the pixmap height // Adjust font size if the total text height exceeds the pixmap height
while (totalTextHeight > pixmapRect.height() && optimalFontSize > 1) { while (totalTextHeight > pixmapRect.height() && optimalFontSize > themeMin) {
--optimalFontSize; --optimalFontSize;
font.setPointSize(optimalFontSize); font.setPointSize(optimalFontSize);
painter.setFont(font); painter.setFont(font);
@ -297,7 +300,9 @@ QSize CardInfoPictureWithTextOverlayWidget::minimumSizeHint() const
// Get the font metrics for the overlay text // Get the font metrics for the overlay text
QFont font = themeManager->appFont(AppFont::CardText); QFont font = themeManager->appFont(AppFont::CardText);
font.setPointSize(fontSize); const int themeMin = std::max(1, themeManager->cardTextMinSize());
const int themeMax = std::max(themeMin, std::max(1, themeManager->cardTextMaxSize()));
font.setPointSize(hasExplicitFontSize ? std::max(1, fontSize) : themeMax);
const QFontMetrics fontMetrics(font); const QFontMetrics fontMetrics(font);
// Calculate the height required for the text // Calculate the height required for the text

View file

@ -60,6 +60,7 @@ private:
QColor textColor; QColor textColor;
QColor outlineColor; QColor outlineColor;
int fontSize; int fontSize;
bool hasExplicitFontSize = false;
Qt::Alignment textAlignment; Qt::Alignment textAlignment;
bool highlighted; bool highlighted;
}; };

View file

@ -283,20 +283,11 @@ AppearanceSettingsPage::AppearanceSettingsPage()
connect(&roundCardCornersCheckBox, &QAbstractButton::toggled, &settings.cardsDisplay(), connect(&roundCardCornersCheckBox, &QAbstractButton::toggled, &settings.cardsDisplay(),
&CardsDisplaySettings::setRoundCardCorners); &CardsDisplaySettings::setRoundCardCorners);
connect(&maxFontSizeForCardsEdit, qOverload<int>(&QSpinBox::valueChanged), &settings.appearance(),
&AppearanceSettings::setMaxFontSize);
maxFontSizeForCardsEdit.setValue(settings.appearance().getMaxFontSize());
maxFontSizeForCardsLabel.setBuddy(&maxFontSizeForCardsEdit);
maxFontSizeForCardsEdit.setMinimum(9);
maxFontSizeForCardsEdit.setMaximum(100);
auto *cardsGrid = new QGridLayout; auto *cardsGrid = new QGridLayout;
cardsGrid->addWidget(&displayCardNamesCheckBox, 0, 0, 1, 2); cardsGrid->addWidget(&displayCardNamesCheckBox, 0, 0, 1, 2);
cardsGrid->addWidget(&autoRotateSidewaysLayoutCardsCheckBox, 1, 0, 1, 2); cardsGrid->addWidget(&autoRotateSidewaysLayoutCardsCheckBox, 1, 0, 1, 2);
cardsGrid->addWidget(&cardScalingCheckBox, 2, 0, 1, 2); cardsGrid->addWidget(&cardScalingCheckBox, 2, 0, 1, 2);
cardsGrid->addWidget(&roundCardCornersCheckBox, 3, 0, 1, 2); cardsGrid->addWidget(&roundCardCornersCheckBox, 3, 0, 1, 2);
cardsGrid->addWidget(&maxFontSizeForCardsLabel, 4, 0, 1, 1);
cardsGrid->addWidget(&maxFontSizeForCardsEdit, 4, 1, 1, 1);
cardsGroupBox = new QGroupBox; cardsGroupBox = new QGroupBox;
cardsGroupBox->setLayout(cardsGrid); cardsGroupBox->setLayout(cardsGrid);
@ -657,7 +648,6 @@ void AppearanceSettingsPage::retranslateUi()
autoRotateSidewaysLayoutCardsCheckBox.setText(tr("Auto-Rotate cards with sideways layout")); autoRotateSidewaysLayoutCardsCheckBox.setText(tr("Auto-Rotate cards with sideways layout"));
cardScalingCheckBox.setText(tr("Scale cards on mouse over")); cardScalingCheckBox.setText(tr("Scale cards on mouse over"));
roundCardCornersCheckBox.setText(tr("Use rounded card corners")); roundCardCornersCheckBox.setText(tr("Use rounded card corners"));
maxFontSizeForCardsLabel.setText(tr("Maximum font size for information displayed on cards:"));
cardLayoutGroupBox->setTitle(tr("Card layout")); cardLayoutGroupBox->setTitle(tr("Card layout"));
verticalCardOverlapPercentLabel.setText( verticalCardOverlapPercentLabel.setText(

View file

@ -71,8 +71,6 @@ private:
QCheckBox autoRotateSidewaysLayoutCardsCheckBox; QCheckBox autoRotateSidewaysLayoutCardsCheckBox;
QCheckBox cardScalingCheckBox; QCheckBox cardScalingCheckBox;
QCheckBox roundCardCornersCheckBox; QCheckBox roundCardCornersCheckBox;
QLabel maxFontSizeForCardsLabel;
QSpinBox maxFontSizeForCardsEdit;
QLabel verticalCardOverlapPercentLabel; QLabel verticalCardOverlapPercentLabel;
QSpinBox verticalCardOverlapPercentBox; QSpinBox verticalCardOverlapPercentBox;

View file

@ -33,16 +33,6 @@ void AppearanceSettings::setStyleUserList(bool _styleUserList)
emit styleUserListChanged(); emit styleUserListChanged();
} }
int AppearanceSettings::getMaxFontSize() const
{
return getValue("maxFontSize", QString(), QString(), 12).toInt();
}
void AppearanceSettings::setMaxFontSize(int _max)
{
setValue(_max, "maxFontSize");
}
QString AppearanceSettings::getHomeTabBackgroundSource() const QString AppearanceSettings::getHomeTabBackgroundSource() const
{ {
return getValue("homeTabBackgroundSource", QString(), QString(), "themed").toString(); return getValue("homeTabBackgroundSource", QString(), QString(), "themed").toString();

View file

@ -24,8 +24,6 @@ public:
void setThemeName(const QString &_themeName); void setThemeName(const QString &_themeName);
[[nodiscard]] bool getStyleUserList() const; [[nodiscard]] bool getStyleUserList() const;
void setStyleUserList(bool _styleUserList); void setStyleUserList(bool _styleUserList);
[[nodiscard]] int getMaxFontSize() const;
void setMaxFontSize(int _max);
[[nodiscard]] QString getHomeTabBackgroundSource() const; [[nodiscard]] QString getHomeTabBackgroundSource() const;
void setHomeTabBackgroundSource(const QString &_backgroundSource); void setHomeTabBackgroundSource(const QString &_backgroundSource);
[[nodiscard]] int getHomeTabBackgroundShuffleFrequency() const; [[nodiscard]] int getHomeTabBackgroundShuffleFrequency() const;

View file

@ -17,6 +17,7 @@
#include <QSharedPointer> #include <QSharedPointer>
#include <QStringList> #include <QStringList>
#include <QStringLiteral> #include <QStringLiteral>
#include <QTextStream>
#include <QVariant> #include <QVariant>
#include <algorithm> #include <algorithm>
#include <utility> #include <utility>
@ -395,6 +396,10 @@ static void migrateAppearanceSettings(const QString &settingsPath, QSettings &gl
{ {
const QMap<QString, QString> appearanceKeyMap = { const QMap<QString, QString> appearanceKeyMap = {
{"theme/name", "appearance/themeName"}, {"theme/name", "appearance/themeName"},
// Retired into the theme's CardText size, but still read here so users
// who have not migrated past global.ini keep their card text size:
// SettingsCache runs migrateSettingsFromGlobalIni() before
// migrateAppearanceMaxFontSizeToTheme().
{"game/maxfontsize", "appearance/maxFontSize"}, {"game/maxfontsize", "appearance/maxFontSize"},
{"home/background", "appearance/homeTabBackgroundSource"}, {"home/background", "appearance/homeTabBackgroundSource"},
{"home/background/shuffleTimer", "appearance/homeTabBackgroundShuffleFrequency"}, {"home/background/shuffleTimer", "appearance/homeTabBackgroundShuffleFrequency"},
@ -424,6 +429,181 @@ static void migrateAppearanceSettings(const QString &settingsPath, QSettings &gl
} }
} }
static QString themesPathForSettings(const QString &settingsPath)
{
QSettings pathsIni(settingsPath + "paths.ini", QSettings::IniFormat);
const QString configured = pathsIni.value("paths/themes").toString().trimmed();
if (!configured.isEmpty()) {
return configured;
}
// Default layout: <data>/themes/ sibling of <data>/settings/.
return QDir::cleanPath(QDir(settingsPath).absoluteFilePath("../themes"));
}
static bool fontsTomlHasExplicitCardTextMax(const QString &filePath)
{
QFile f(filePath);
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
return false;
}
QString currentSection;
QTextStream in(&f);
while (!in.atEnd()) {
QString line = in.readLine().trimmed();
if (line.isEmpty() || line.startsWith('#') || line.startsWith(';')) {
continue;
}
if (line.startsWith('[') && line.endsWith(']')) {
currentSection = line.mid(1, line.length() - 2).trimmed();
continue;
}
if (currentSection.compare("Fonts", Qt::CaseInsensitive) != 0) {
continue;
}
int eq = line.indexOf('=');
if (eq < 0) {
continue;
}
QString key = line.left(eq).trimmed();
QString value = line.mid(eq + 1).trimmed();
for (int i = 1; i < value.size(); ++i) {
if ((value[i] == '#' || value[i] == ';') && value[i - 1].isSpace()) {
value = value.left(i).trimmed();
break;
}
}
if (value.size() >= 2 &&
((value.startsWith('"') && value.endsWith('"')) || (value.startsWith('\'') && value.endsWith('\'')))) {
value = value.mid(1, value.size() - 2).trimmed();
}
if (key.startsWith("AppFont::")) {
key = key.mid(QStringLiteral("AppFont::").size());
}
if (key.compare("CardTextMaxSize", Qt::CaseInsensitive) == 0) {
return true;
}
if (key.compare("CardText", Qt::CaseInsensitive) != 0) {
continue;
}
int comma = value.lastIndexOf(',');
if (comma < 0) {
continue;
}
QString family = value.left(comma).trimmed();
if (family.size() >= 2 &&
((family.startsWith('"') && family.endsWith('"')) || (family.startsWith('\'') && family.endsWith('\'')))) {
family = family.mid(1, family.size() - 2).trimmed();
}
bool ok = false;
int pointSize = value.mid(comma + 1).trimmed().toInt(&ok);
if (!ok || family.isEmpty() || pointSize <= 0) {
continue;
}
return true;
}
return false;
}
static bool seedCardTextMaxIntoThemeDir(const QString &themeDirPath, int maxSize)
{
if (themeDirPath.isEmpty() || maxSize <= 0) {
return false;
}
QDir dir(themeDirPath);
if (!dir.exists()) {
return false;
}
const QString filePath = dir.absoluteFilePath("fonts.toml");
if (fontsTomlHasExplicitCardTextMax(filePath)) {
return false;
}
QString existing;
if (QFile::exists(filePath)) {
QFile in(filePath);
if (!in.open(QIODevice::ReadOnly | QIODevice::Text)) {
return false;
}
existing = QString::fromUtf8(in.readAll());
}
// Size-only key: seeding the CardText family here would pin it to Serif
// for the whole board (names, player labels, the completer), which is not
// what the retired maxFontSize setting meant. Let the family keep its
// existing resolution and only constrain the size.
const QString entry = QString("%1 = %2\n").arg(QStringLiteral("CardTextMaxSize"), -20).arg(maxSize);
QString out;
if (existing.isEmpty()) {
out = "[Fonts]\n" + entry + "\n";
} else if (existing.contains("[Fonts]", Qt::CaseInsensitive)) {
out = existing;
if (!out.endsWith('\n')) {
out += '\n';
}
out += entry;
} else {
out = existing;
if (!out.endsWith('\n')) {
out += '\n';
}
out += "\n[Fonts]\n" + entry + "\n";
}
QFile f(filePath);
if (!f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
return false;
}
QTextStream stream(&f);
stream << out;
return true;
}
static bool appearanceMaxFontSizeMigrationPending(const QString &settingsPath)
{
if (!QFile::exists(settingsPath + "appearance.ini")) {
return false;
}
QSettings appearanceIni(settingsPath + "appearance.ini", QSettings::IniFormat);
return appearanceIni.contains("appearance/maxFontSize");
}
bool SettingsMigration::migrateAppearanceMaxFontSizeToTheme(const QString &settingsPath)
{
if (!appearanceMaxFontSizeMigrationPending(settingsPath)) {
return false;
}
QSettings appearanceIni(settingsPath + "appearance.ini", QSettings::IniFormat);
// Retired default was 12 in AppearanceSettings::getMaxFontSize; only
// non-default values are seeded, default users rely on the theme fallback.
const int legacy = appearanceIni.value("appearance/maxFontSize").toInt();
if (legacy != 12 && legacy > 0) {
const QString themesPath = themesPathForSettings(settingsPath);
QDir themesDir(themesPath);
const QStringList themes = themesDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
for (const QString &theme : themes) {
seedCardTextMaxIntoThemeDir(themesDir.absoluteFilePath(theme), legacy);
}
// Shipped themes (Fabric, Leather, ...) resolve to the system theme
// directory unless the user has already saved something for them, and
// System/Fusion only ever exist in the user directory. Seeding existing
// folders alone would therefore drop the retired size for every user on
// a stock theme, so create the active theme's user directory the same
// way ThemeManager::writableThemeDir() does for palette and font saves.
const QString activeTheme = appearanceIni.value("appearance/themeName").toString().trimmed();
if (!activeTheme.isEmpty() && !themesDir.exists(activeTheme) && themesDir.mkpath(activeTheme)) {
seedCardTextMaxIntoThemeDir(themesDir.absoluteFilePath(activeTheme), legacy);
}
}
appearanceIni.remove("appearance/maxFontSize");
appearanceIni.sync();
return true;
}
static void migrateNetworkSettings(const QString &settingsPath, QSettings &globalIni) static void migrateNetworkSettings(const QString &settingsPath, QSettings &globalIni)
{ {
const QMap<QString, QString> networkKeyMap = { const QMap<QString, QString> networkKeyMap = {
@ -821,6 +1001,10 @@ bool SettingsMigration::isMigrationPending(const QString &settingsPath)
return true; return true;
} }
if (appearanceMaxFontSizeMigrationPending(settingsPath)) {
return true;
}
QSettings personalIni(settingsPath + "personal.ini", QSettings::IniFormat); QSettings personalIni(settingsPath + "personal.ini", QSettings::IniFormat);
if (personalIni.value("migration/legacy_complete", false).toBool()) { if (personalIni.value("migration/legacy_complete", false).toBool()) {
return false; return false;

View file

@ -18,6 +18,15 @@ public:
*/ */
static bool migrateGameFiltersToCamelCase(const QString &settingsPath); static bool migrateGameFiltersToCamelCase(const QString &settingsPath);
/**
* Moves a non-default appearance/maxFontSize value into each user-writable theme
* directory lacking an explicit CardText max (creating/merging fonts.toml, never
* overwriting an explicit max), then deletes the legacy key. Default (12) or absent
* values only delete the key and create no files, since the ThemeManager fallback
* already covers them. Idempotent, safe to run on every startup.
*/
static bool migrateAppearanceMaxFontSizeToTheme(const QString &settingsPath);
/** /**
* Returns true when at least one of the two migrations would still do work, i.e. when a * 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. * backup of the settings directory is worth taking before they run.

View file

@ -10,6 +10,7 @@
#include <QTemporaryDir> #include <QTemporaryDir>
#include <QTextStream> #include <QTextStream>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <string>
namespace namespace
{ {
@ -186,6 +187,154 @@ TEST(FontConfigTest, InequalityComparesRoles)
EXPECT_NE(left, right); EXPECT_NE(left, right);
} }
TEST(FontConfigTest, CardTextMinRoundTrip)
{
QTemporaryDir tmp;
ASSERT_TRUE(tmp.isValid());
FontConfig cfg;
cfg.fonts[AppFont::CardText] = makeFont("Noto Serif", 14);
cfg.cardTextMinSize = 3;
ASSERT_TRUE(cfg.hasFonts());
const QString filePath = writeTempFile(tmp, "fonts.toml", cfg.toToml());
const FontConfig loaded = FontConfig::fromFile(filePath);
EXPECT_EQ(loaded, cfg);
EXPECT_EQ(loaded.fonts[AppFont::CardText].pointSize(), 14);
EXPECT_EQ(loaded.cardTextMinSize, 3);
}
TEST(FontConfigTest, CardTextMinDefaultIsOmitted)
{
FontConfig cfg;
cfg.fonts[AppFont::CardText] = makeFont("Noto Serif", 12);
EXPECT_EQ(cfg.cardTextMinSize, 1);
const QString toml = cfg.toToml();
EXPECT_TRUE(toml.contains(QStringLiteral("CardText")));
EXPECT_FALSE(toml.contains(QStringLiteral("CardTextMinSize")));
QTemporaryDir tmp;
ASSERT_TRUE(tmp.isValid());
const QString filePath = writeTempFile(tmp, "fonts.toml", toml);
EXPECT_EQ(FontConfig::fromFile(filePath).cardTextMinSize, 1);
}
TEST(FontConfigTest, MalformedMinIsSkipped)
{
QTemporaryDir tmp;
ASSERT_TRUE(tmp.isValid());
const QString filePath = writeTempFile(tmp, "fonts.toml",
"[Fonts]\n"
"CardText = \"Noto Serif, 14\"\n"
"CardTextMinSize = big\n");
// Each malformed value parses independently, so rewrite the file per case.
const QStringList badValues = {"big", "0", "-3", "", "Inter, 10"};
for (const QString &bad : badValues) {
const QString badPath = writeTempFile(
tmp, "bad.toml", QString("[Fonts]\nCardText = \"Noto Serif, 14\"\nCardTextMinSize = %1\n").arg(bad));
const FontConfig loaded = FontConfig::fromFile(badPath);
EXPECT_EQ(loaded.cardTextMinSize, 1) << "bad value: " << bad.toStdString();
ASSERT_TRUE(loaded.fonts.contains(AppFont::CardText));
EXPECT_EQ(loaded.fonts[AppFont::CardText].pointSize(), 14);
}
// The valid file still loads.
EXPECT_EQ(FontConfig::fromFile(filePath).cardTextMinSize, 1);
}
TEST(FontConfigTest, CardTextMaxRoundTrip)
{
QTemporaryDir tmp;
ASSERT_TRUE(tmp.isValid());
FontConfig cfg;
cfg.fonts[AppFont::CardText] = makeFont("Noto Serif", 14);
cfg.cardTextMaxSize = 18;
ASSERT_TRUE(cfg.hasFonts());
const QString filePath = writeTempFile(tmp, "fonts.toml", cfg.toToml());
const FontConfig loaded = FontConfig::fromFile(filePath);
EXPECT_EQ(loaded, cfg);
EXPECT_EQ(loaded.cardTextMaxSize, 18);
EXPECT_EQ(loaded.fonts[AppFont::CardText].pointSize(), 14);
}
TEST(FontConfigTest, CardTextMaxSizeWithoutFamily)
{
// The retired maxFontSize setting migrates to a size-only key, so the
// family keeps its existing resolution instead of being pinned to Serif.
FontConfig cfg;
cfg.cardTextMaxSize = 18;
ASSERT_TRUE(cfg.hasFonts());
QTemporaryDir tmp;
ASSERT_TRUE(tmp.isValid());
const QString filePath = writeTempFile(tmp, "fonts.toml", cfg.toToml());
const FontConfig loaded = FontConfig::fromFile(filePath);
EXPECT_TRUE(loaded.fonts.isEmpty());
EXPECT_EQ(loaded.cardTextMaxSize, 18);
EXPECT_EQ(loaded, cfg);
}
TEST(FontConfigTest, CardTextMaxDefaultIsOmitted)
{
FontConfig cfg;
cfg.fonts[AppFont::CardText] = makeFont("Noto Serif", 12);
EXPECT_EQ(cfg.cardTextMaxSize, 0);
const QString toml = cfg.toToml();
EXPECT_FALSE(toml.contains(QStringLiteral("CardTextMaxSize")));
QTemporaryDir tmp;
ASSERT_TRUE(tmp.isValid());
const QString filePath = writeTempFile(tmp, "fonts.toml", toml);
EXPECT_EQ(FontConfig::fromFile(filePath).cardTextMaxSize, 0);
}
TEST(FontConfigTest, MalformedMaxIsSkipped)
{
QTemporaryDir tmp;
ASSERT_TRUE(tmp.isValid());
const QStringList badValues = {"big", "0", "-3", "", "Inter, 18"};
for (const QString &bad : badValues) {
const QString badPath = writeTempFile(
tmp, "bad.toml", QString("[Fonts]\nCardText = \"Noto Serif, 14\"\nCardTextMaxSize = %1\n").arg(bad));
const FontConfig loaded = FontConfig::fromFile(badPath);
EXPECT_EQ(loaded.cardTextMaxSize, 0) << "bad value: " << bad.toStdString();
ASSERT_TRUE(loaded.fonts.contains(AppFont::CardText));
EXPECT_EQ(loaded.fonts[AppFont::CardText].pointSize(), 14);
}
}
TEST(FontConfigTest, InequalityComparesMinSize)
{
FontConfig left;
left.fonts[AppFont::CardText] = makeFont("Noto Serif", 12);
left.cardTextMinSize = 1;
FontConfig right = left;
right.cardTextMinSize = 2;
EXPECT_NE(left, right);
}
TEST(FontConfigTest, InequalityComparesMaxSize)
{
FontConfig left;
left.fonts[AppFont::CardText] = makeFont("Noto Serif", 12);
left.cardTextMaxSize = 12;
FontConfig right = left;
right.cardTextMaxSize = 18;
EXPECT_NE(left, right);
}
// Every test brings its own main(): GTest is not guaranteed to be found on the build machines, and // Every test brings its own main(): GTest is not guaranteed to be found on the build machines, and
// the fallback then links the bare gtest library without gtest_main. // the fallback then links the bare gtest library without gtest_main.
int main(int argc, char **argv) int main(int argc, char **argv)

View file

@ -461,19 +461,6 @@ TEST_F(SettingsDefaultsTest, Appearance_StyleUserList_Default)
ASSERT_EQ(s.getStyleUserList(), true); ASSERT_EQ(s.getStyleUserList(), true);
} }
TEST_F(SettingsDefaultsTest, Appearance_MaxFontSize_Default)
{
AppearanceSettings s(settingsPath, nullptr);
ASSERT_EQ(s.getMaxFontSize(), 12);
}
TEST_F(SettingsDefaultsTest, Appearance_MaxFontSize_SetAndGet)
{
AppearanceSettings s(settingsPath, nullptr);
s.setMaxFontSize(14);
ASSERT_EQ(s.getMaxFontSize(), 14);
}
TEST_F(SettingsDefaultsTest, Appearance_HomeTabBackgroundSource_Default) TEST_F(SettingsDefaultsTest, Appearance_HomeTabBackgroundSource_Default)
{ {
AppearanceSettings s(settingsPath, nullptr); AppearanceSettings s(settingsPath, nullptr);

View file

@ -984,6 +984,198 @@ TEST_F(SettingsMigrationTest, IsNotMigrationPendingWithoutLegacyData)
ASSERT_FALSE(SettingsMigration::isMigrationPending(settingsPath)); ASSERT_FALSE(SettingsMigration::isMigrationPending(settingsPath));
} }
TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_NonDefaultSeedsThemeAndDeletesKey)
{
const QString themesPath = tempDir.path() + "/themes";
ASSERT_TRUE(QDir().mkpath(themesPath + "/MyTheme"));
{
QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat);
paths.setValue("paths/themes", themesPath);
paths.sync();
}
{
QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat);
appearance.setValue("appearance/maxFontSize", 18);
appearance.sync();
}
ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath));
ASSERT_FALSE(readFromIni("appearance.ini", "appearance/maxFontSize").isValid());
const QString fontsPath = themesPath + "/MyTheme/fonts.toml";
ASSERT_TRUE(QFile::exists(fontsPath));
QFile f(fontsPath);
ASSERT_TRUE(f.open(QIODevice::ReadOnly | QIODevice::Text));
const QString content = QString::fromUtf8(f.readAll());
EXPECT_TRUE(content.contains("CardTextMaxSize"));
EXPECT_TRUE(content.contains("18"));
// Size-only: the migration must not pin the family, or every CardText
// surface switches to Serif for users who never chose it.
EXPECT_FALSE(content.contains("Serif"));
ASSERT_FALSE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath));
}
TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_SeedsActiveStockTheme)
{
// A stock theme resolves to the system directory, so there is no user theme
// folder to seed. The migration has to create the active theme's folder,
// otherwise the retired size is deleted without landing anywhere.
const QString themesPath = tempDir.path() + "/themes";
{
QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat);
paths.setValue("paths/themes", themesPath);
paths.sync();
}
{
QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat);
appearance.setValue("appearance/themeName", "Fabric");
appearance.setValue("appearance/maxFontSize", 18);
appearance.sync();
}
ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath));
ASSERT_FALSE(readFromIni("appearance.ini", "appearance/maxFontSize").isValid());
const QString fontsPath = themesPath + "/Fabric/fonts.toml";
ASSERT_TRUE(QFile::exists(fontsPath));
QFile f(fontsPath);
ASSERT_TRUE(f.open(QIODevice::ReadOnly | QIODevice::Text));
const QString content = QString::fromUtf8(f.readAll());
EXPECT_TRUE(content.contains("CardTextMaxSize"));
EXPECT_TRUE(content.contains("18"));
}
TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_KeepsExistingActiveTheme)
{
const QString themesPath = tempDir.path() + "/themes";
ASSERT_TRUE(QDir().mkpath(themesPath + "/Fabric"));
{
QFile f(themesPath + "/Fabric/fonts.toml");
ASSERT_TRUE(f.open(QIODevice::WriteOnly | QIODevice::Text));
QTextStream out(&f);
out << "[Fonts]\nCardText = \"Inter, 10\"\n";
}
{
QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat);
paths.setValue("paths/themes", themesPath);
paths.sync();
}
{
QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat);
appearance.setValue("appearance/themeName", "Fabric");
appearance.setValue("appearance/maxFontSize", 18);
appearance.sync();
}
ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath));
QFile f(themesPath + "/Fabric/fonts.toml");
ASSERT_TRUE(f.open(QIODevice::ReadOnly | QIODevice::Text));
const QString content = QString::fromUtf8(f.readAll());
EXPECT_TRUE(content.contains("Inter, 10"));
EXPECT_FALSE(content.contains("18"));
}
TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_DefaultDeletesKeyWithoutFiles)
{
const QString themesPath = tempDir.path() + "/themes";
ASSERT_TRUE(QDir().mkpath(themesPath + "/MyTheme"));
{
QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat);
paths.setValue("paths/themes", themesPath);
paths.sync();
}
{
QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat);
appearance.setValue("appearance/maxFontSize", 12);
appearance.sync();
}
ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath));
ASSERT_FALSE(readFromIni("appearance.ini", "appearance/maxFontSize").isValid());
ASSERT_FALSE(QFile::exists(themesPath + "/MyTheme/fonts.toml"));
}
TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_DoesNotOverwriteExplicitMax)
{
const QString themesPath = tempDir.path() + "/themes";
ASSERT_TRUE(QDir().mkpath(themesPath + "/MyTheme"));
{
QFile f(themesPath + "/MyTheme/fonts.toml");
ASSERT_TRUE(f.open(QIODevice::WriteOnly | QIODevice::Text));
QTextStream out(&f);
out << "[Fonts]\nCardText = \"Inter, 10\"\n";
}
{
QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat);
paths.setValue("paths/themes", themesPath);
paths.sync();
}
{
QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat);
appearance.setValue("appearance/maxFontSize", 18);
appearance.sync();
}
ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath));
QFile f(themesPath + "/MyTheme/fonts.toml");
ASSERT_TRUE(f.open(QIODevice::ReadOnly | QIODevice::Text));
const QString content = QString::fromUtf8(f.readAll());
EXPECT_TRUE(content.contains("10"));
EXPECT_FALSE(content.contains("18"));
}
TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_MergesWithOtherRoles)
{
const QString themesPath = tempDir.path() + "/themes";
ASSERT_TRUE(QDir().mkpath(themesPath + "/MyTheme"));
{
QFile f(themesPath + "/MyTheme/fonts.toml");
ASSERT_TRUE(f.open(QIODevice::WriteOnly | QIODevice::Text));
QTextStream out(&f);
out << "[Fonts]\nInterfaceDefault = \"Inter, 10\"\n";
}
{
QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat);
paths.setValue("paths/themes", themesPath);
paths.sync();
}
{
QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat);
appearance.setValue("appearance/maxFontSize", 20);
appearance.sync();
}
ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath));
QFile f(themesPath + "/MyTheme/fonts.toml");
ASSERT_TRUE(f.open(QIODevice::ReadOnly | QIODevice::Text));
const QString content = QString::fromUtf8(f.readAll());
EXPECT_TRUE(content.contains("InterfaceDefault"));
EXPECT_TRUE(content.contains("CardTextMaxSize"));
EXPECT_TRUE(content.contains("20"));
}
TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_AbsentKeyDoesNothing)
{
ASSERT_FALSE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath));
}
TEST_F(SettingsMigrationTest, IsMigrationPendingWhenMaxFontSizePresent)
{
{
QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat);
appearance.setValue("appearance/maxFontSize", 18);
appearance.sync();
}
ASSERT_TRUE(SettingsMigration::isMigrationPending(settingsPath));
}
} // namespace } // namespace
int main(int argc, char **argv) int main(int argc, char **argv)