Compare commits

...

26 commits

Author SHA1 Message Date
Lukas Brübach
45ec061e4e [Fonts] Polish font editor accessibility and note text 2026-11-03 18:22:40 +01:00
Lukas Brübach
97ae1b93db [Fonts] Keep the DeckList fallback on the application font
DeckList is read by the deck editor headers, the filter tree and the deck
view too, not only by the two surfaces that hardcoded Serif. Resolving the
unset role to Serif in appFont() made every user without a fonts.toml see
Serif outside the deck list.

- appFont(DeckList) falls back to the application font again.
- ThemeManager::deckListFont() resolves the role and keeps the Serif
  fallback for the two historical sites: the deck view zone headers and
  the printed deck list.
- DeckView no longer repaints its viewport on fontsChanged; touching the
  scene items already schedules the repaint in every attached view.
2026-11-03 18:22:40 +01:00
Lukas Brübach
17061980cc [Fonts] Restore DeckList Serif default and live-update all role readers 2026-11-03 18:22:40 +01:00
Lukas Brübach
ad860eb932 [Fonts] Fix font preview leaks and reuse default-font loader 2026-11-03 18:22:39 +01:00
Lukas Brübach
28c200e950 [Fonts] Fix pixel sizes and game overlay fonts from review
- Set the sizes the stylesheets carried with setPixelSize() rather than
  setPointSize(): home buttons (34), banner (24), home group box (20),
  the placeholder labels (14), the analytics drag handle (10), the EDHREC
  navigation buttons (24), the user info popup (11/14/11/10/11) and the
  game overlays (14/16/12). Points at 96 DPI were ~33% larger.
- The drag/total counters and tally columns keep the monospace family the
  stylesheets set, so digit widths stop changing as counts tick and
  white-space: pre stays aligned.
- applyDeckListFont() drops the cached tally rows before re-measuring, so
  previewing a larger DeckList font no longer leaves the tally clipped.
- Tally labels and the constructor/fontsChanged pairs in the shared-deck
  preview, quick setup, analytics panel, home box and public decks build
  their font once in a local lambda instead of twice.
2026-11-03 18:22:39 +01:00
Lukas Brübach
60f7710d9f [Fonts] Drop unused lambda captures in font-refresh handlers 2026-11-03 18:22:39 +01:00
Lukas Brübach
d26a7b213f [Fonts] Migrate DeckList and InterfaceDefault surfaces to theme roles 2026-11-03 18:22:39 +01:00
Lukas Brübach
a7e151d871 [Fonts] Keep CardText call sites on the app font and fix px sizes
- CardText's unset fallback is qApp->font() again: only the three counter
  painters (paintNumberEllipse, GeneralCounter, PlayerCounter) were Serif
  before the role existed, and they now ask for it through
  ThemeManager::cardTextFont(). Card names, player names, counters, the
  overlay, the Archidekt and EDHREC labels and the completer delegate keep the
  app font they used before.
- Use setPixelSize for the EDHREC card name (20px) and commander name (28px)
  and details (16px) sizes: the stylesheets said px, and setPointSize made
  them about a third larger.
2026-11-03 18:22:39 +01:00
Lukas Brübach
2593824e93 [Fonts] Migrate CardText surfaces to theme font role 2026-11-03 18:22:39 +01:00
Lukas Brübach
4e78edf794 [Fonts] Keep ChatView on the app font and share the report log font
- ChatLog's unset fallback is qApp->font() again: ChatView never set a font
  before the role existed, so an unset role must not switch chat and the game
  log to monospace at the system GeneralFont size.
- Move the monospace fallback the report dialogs used before into
  report_utils::reportLogFont(), which returns the theme's ChatLog role when it
  is set and the old monospace otherwise. tab_report, dlg_my_reports and
  dlg_report_user now share it, so they cannot drift and they honour a themed
  size instead of overriding it.
2026-11-03 18:22:39 +01:00
Lukas Brübach
1bdd43d921 [Fonts] Migrate ChatLog surfaces to theme font role 2026-11-03 18:22:39 +01:00
Lukas Brübach
77ed6550f9 [Fonts] Make the editor's fallback and Reset preview-independent
- Add ThemeManager::defaultAppFont(), which resolves an unset role against the
  startup font instead of currentFonts, and use it in FontGridWidget::loadFonts
  so the grid never shows the live preview as the theme's default.
- Reload the live theme in PaletteEditorDialog::onReset before re-reading the
  grid, so Reset restores the saved fonts before loadFonts resolves fallbacks.
2026-11-03 18:22:39 +01:00
Lukas Brübach
b57cd77986 [Fonts] Add per-purpose font section to palette editor 2026-11-03 18:22:39 +01:00
Lukas Brübach
4a60b2fde2 [Fonts] Restore the startup font when a config has no InterfaceDefault
- Capture the untouched application font next to the default palette and use
  it in previewFonts' else branch, so a config without InterfaceDefault
  restores the startup font instead of leaving the last preview applied.
- Drop the unpolish/polish loop from previewFonts: QApplication::setFont
  already notifies every widget, and the loop ran on every editor auto-apply.
2026-11-03 18:22:39 +01:00
Lukas Brübach
134c830d42 [Fonts] Wire FontConfig into ThemeManager with appFont and preview 2026-11-03 18:22:39 +01:00
Lukas Brübach
d34bb81884 [Fonts] Add AppFont roles and FontConfig fonts.toml round-trip 2026-11-03 18:22:32 +01:00
BruebachL
c72192fd81
[Format] Small fixup (#7768)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / vcpkg baseline (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Desktop / Prune caches (push) Waiting to run
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [Format] Small fixup.

* [CardDB] Stop leaking the loader's mutexes.

Both mutexes were heap-allocated through default member initializers and
never freed, so every CardDatabase destruction leaked 16 bytes. LeakSanitizer
flags this while discovering tests that create the database in main.

Hold them by value instead, as the loader is a QObject and therefore neither
copyable nor movable.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-11-03 15:59:32 +01:00
Bryce!
9588e1960d
Preserve saved deck formats through database reload and view refresh (#7745) 2026-11-03 11:10:50 +01:00
Bryce!
fd2a99e897
Respect nonempty Qt hints and explain discovery failures [skip ci] (#7733) 2026-11-03 10:41:47 +01:00
Bryce!
dc4aa19d23
Import Moxfield signature spells into the deck [skip ci] (#7751)
Co-authored-by: Codex <codex@openai.com>
2026-11-03 10:40:28 +01:00
Bryce!
ada8f399f1
Log server identification and connection lifecycle (#7731)
* Log remote server identification and connection lifecycle [skip ci]

* Update libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp

Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>

---------

Co-authored-by: BruebachL <44814898+BruebachL@users.noreply.github.com>
Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>
2026-11-03 10:38:54 +01:00
Jurriaan
bc09ec6afe
[Client] Make DeckStateManager::decrementCard remove tokens from the token zone if the token is not in the given zone (#7588)
* Fixed decrementCard for tokens not using the token zone

* Removed dead function actDecrementCard

Updated documentation

* Fixed no-op, added documentation

* Removed stray blank line

* Allow finding card nodes even if no database entry exists for the given card

* Added DeckStateManager tests

* Added tokens on mainboard test

* Added if checks and sorted sources

Added if checks

* Library include fix

* Renamed zone to targetZone

* Revert "Allow finding card nodes even if no database entry exists for the given card"

This reverts commit 88578cd0d9.

* Combined loader_local_matching_test and deck_state_manager_test settings sources

* Added settings sandbox, refactor

* Format

* Renamed return variable

* Flattened if statement

* Style fix
2026-11-03 10:37:17 +01:00
Jurriaan
bfb53c5def
[Client] Visual Deck Editor: Added more mouse click modifiers to add/decrement cards to specific zones (#7589)
* Make visual database mouse click modifiers properly add/decrement different card zones

* Use event->modifiers(), refactor

* Removed QApplication include

* Added visual database display documentation page

* Minor fixes

* Added documentation for the mouse interaction of the table view
2026-11-03 10:35:58 +01:00
PILIX123
381dd21e8c
[Client] Add option to load decks from ManaBox (#7642)
* [Client] Add option to load decks from ManaBox

* [Client] Fix PR comments

* [Client] Fix UI and transaltion requests

* [Client] Fix for UI and parser not doing what expected when loading maybe board

* Add ManaBox support to docs

As pointed out in review comments

* [Client] Fix final PR comments

---------

Co-authored-by: tooomm <tooomm@users.noreply.github.com>
2026-11-03 10:33:21 +01:00
transifex-integration[bot]
c5b776ca46
Translate oracle/oracle_en@source.ts in de (#7767)
100% translated source file: 'oracle/oracle_en@source.ts'
on 'de'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2026-11-03 10:31:12 +01:00
BruebachL
dd7ee3541c
[PictureLoader] Keep the download log from going quadratic (#7766)
* [PictureLoader] Keep the download log from going quadratic

A fast scrollbar sweep over the visual card database requests a picture for
every cell it paints, so one gesture can ask for thousands of downloads. The
status bar built a seven-widget log entry for each of them and answered every
completion with findChildren() over the whole, unbounded log, rebuilding
url.toString() per child: O(n) per completion, O(n^2) per burst.

Profiling a sweep on master put 47% of all cycles on the GUI thread, with
qt_qFindChildren_helper at 12.4% of the process (a quarter of that thread) and
QWidgetPrivate::reparentFocusChildren at 4.1%. LagMonitor reported stalls of
3-18 s, growing with the backlog.

Keep at most MAX_LOG_ENTRIES entries and index them by URL, so a completion
and the 1 Hz cleanup only visit the matching entries. The progress bar follows
the entries that remain: dropping a finished entry moves both counters back,
dropping a pending one only its maximum. The log is a diagnostic view, not a
queue - the downloads themselves are unaffected.

* fixup! [PictureLoader] Keep the download log from going quadratic

Make the URL index a QMultiHash and say why: a deferred retry re-queues the same URL while its first log entry is still pending, so one URL can back several live entries.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-11-03 10:17:53 +01:00
111 changed files with 2299 additions and 237 deletions

View file

@ -71,6 +71,8 @@ if(WIN32 OR USE_VCPKG)
) )
# Qt path set by user or env var # Qt path set by user or env var
if(QTDIR if(QTDIR
OR QTDIR32
OR QTDIR64
OR DEFINED ENV{QTDIR} OR DEFINED ENV{QTDIR}
OR DEFINED ENV{QTDIR32} OR DEFINED ENV{QTDIR32}
OR DEFINED ENV{QTDIR64} OR DEFINED ENV{QTDIR64}
@ -236,13 +238,13 @@ else()
set(_lib_suffix 32) set(_lib_suffix 32)
endif() endif()
if(DEFINED QTDIR${_lib_suffix}) if(NOT "${QTDIR${_lib_suffix}}" STREQUAL "")
list(APPEND CMAKE_PREFIX_PATH "${QTDIR${_lib_suffix}}") list(APPEND CMAKE_PREFIX_PATH "${QTDIR${_lib_suffix}}")
elseif(DEFINED QTDIR) elseif(NOT "${QTDIR}" STREQUAL "")
list(APPEND CMAKE_PREFIX_PATH "${QTDIR}") list(APPEND CMAKE_PREFIX_PATH "${QTDIR}")
elseif(DEFINED ENV{QTDIR${_lib_suffix}}) elseif(NOT "$ENV{QTDIR${_lib_suffix}}" STREQUAL "")
list(APPEND CMAKE_PREFIX_PATH "$ENV{QTDIR${_lib_suffix}}") list(APPEND CMAKE_PREFIX_PATH "$ENV{QTDIR${_lib_suffix}}")
elseif(DEFINED ENV{QTDIR}) elseif(NOT "$ENV{QTDIR}" STREQUAL "")
list(APPEND CMAKE_PREFIX_PATH "$ENV{QTDIR}") list(APPEND CMAKE_PREFIX_PATH "$ENV{QTDIR}")
endif() endif()

View file

@ -1,6 +1,6 @@
# Find a compatible Qt version # Find a compatible Qt version
# Inputs: WITH_SERVER, WITH_CLIENT, WITH_ORACLE # Inputs: WITH_SERVER, WITH_CLIENT, WITH_ORACLE
# Optional Input: QT6_DIR -- Hint as to where Qt6 lives on the system # Optional Input: Qt6_DIR -- Directory containing Qt6Config.cmake
# Output: COCKATRICE_QT_VERSION_NAME -- Example values: Qt6 # Output: COCKATRICE_QT_VERSION_NAME -- Example values: Qt6
# Output: SERVATRICE_QT_MODULES # Output: SERVATRICE_QT_MODULES
# Output: COCKATRICE_QT_MODULES # Output: COCKATRICE_QT_MODULES
@ -57,7 +57,17 @@ find_package(
QUIET HINTS ${Qt6_DIR} QUIET HINTS ${Qt6_DIR}
) )
if(NOT Qt6_FOUND) if(NOT Qt6_FOUND)
message(FATAL_ERROR "No suitable version of Qt was found") set(_QT_DIAGNOSTIC "")
if(Qt6_NOT_FOUND_MESSAGE)
set(_QT_DIAGNOSTIC "\nQt package diagnostic: ${Qt6_NOT_FOUND_MESSAGE}")
endif()
message(FATAL_ERROR
"No suitable version of Qt was found. Qt 6.4.2 or newer with these components is required: "
"${REQUIRED_QT_COMPONENTS};Linguist.\n"
"Set QTDIR or CMAKE_PREFIX_PATH to the Qt installation prefix, or Qt6_DIR to the directory "
"containing Qt6Config.cmake."
"${_QT_DIAGNOSTIC}"
)
endif() endif()
set(COCKATRICE_QT_VERSION_NAME Qt6) set(COCKATRICE_QT_VERSION_NAME Qt6)

View file

@ -154,6 +154,7 @@ set(cockatrice_SOURCES
src/interface/palette_editor/palette_generator.cpp src/interface/palette_editor/palette_generator.cpp
src/interface/palette_editor/quick_setup_panel.cpp src/interface/palette_editor/quick_setup_panel.cpp
src/interface/palette_editor/palette_grid_widget.cpp src/interface/palette_editor/palette_grid_widget.cpp
src/interface/palette_editor/font_grid_widget.cpp
src/interface/palette_editor/palette_editor_dialog.cpp src/interface/palette_editor/palette_editor_dialog.cpp
src/interface/widgets/cards/additional_info/color_identity_widget.cpp src/interface/widgets/cards/additional_info/color_identity_widget.cpp
src/interface/widgets/cards/additional_info/deck_color_identity.cpp src/interface/widgets/cards/additional_info/deck_color_identity.cpp

View file

@ -15,14 +15,18 @@ static const QString ARCHIDEKT_SUFFIX = "/?format=json";
static const QString MOXFIELD_BASE = "https://api.moxfield.com/v2/decks/all/"; static const QString MOXFIELD_BASE = "https://api.moxfield.com/v2/decks/all/";
static const QString MOXFIELD_SUFFIX = "/"; static const QString MOXFIELD_SUFFIX = "/";
static const QString DECKSTATS_BASE = "https://deckstats.net/decks/";
static const QString DECKSTATS_SUFFIX = "?include_comments=1&export_mtgarena=1"; static const QString DECKSTATS_SUFFIX = "?include_comments=1&export_mtgarena=1";
static const QString MANABOX_BASE = "https://cloud.manabox.app/decks/";
bool parseDeckUrl(const QString &url, ParsedDeckInfo &outInfo) bool parseDeckUrl(const QString &url, ParsedDeckInfo &outInfo)
{ {
static QRegularExpression rxTappedOut("tappedout\\.net/(?:mtg-decks/)?([^/?#]+)"); static QRegularExpression rxTappedOut("tappedout\\.net/(?:mtg-decks/)?([^/?#]+)");
static QRegularExpression rxArchidekt("archidekt\\.com/decks/(\\d+)"); static QRegularExpression rxArchidekt("archidekt\\.com/decks/(\\d+)");
static QRegularExpression rxMoxfield("moxfield\\.com/decks/([a-zA-Z0-9_-]+)"); static QRegularExpression rxMoxfield("moxfield\\.com/decks/([a-zA-Z0-9_-]+)");
static QRegularExpression rxDeckstats("deckstats\\.net/decks/(\\d+/[a-zA-Z0-9_-]+)"); static QRegularExpression rxDeckstats("deckstats\\.net/decks/(\\d+/[a-zA-Z0-9_-]+)");
static QRegularExpression rxManabox("manabox\\.app/decks/([a-zA-Z0-9_-]+)");
QRegularExpressionMatch match; QRegularExpressionMatch match;
@ -49,11 +53,18 @@ bool parseDeckUrl(const QString &url, ParsedDeckInfo &outInfo)
return true; return true;
} else if ((match = rxDeckstats.match(url)).hasMatch()) { } else if ((match = rxDeckstats.match(url)).hasMatch()) {
QString deckPath = match.captured(1); QString deckPath = match.captured(1);
outInfo = ParsedDeckInfo{.baseUrl = "https://deckstats.net/decks/", outInfo = ParsedDeckInfo{.baseUrl = DECKSTATS_BASE,
.deckID = deckPath, .deckID = deckPath,
.fullUrl = "https://deckstats.net/decks/" + deckPath + DECKSTATS_SUFFIX, .fullUrl = DECKSTATS_BASE + deckPath + DECKSTATS_SUFFIX,
.provider = DeckProvider::Deckstats}; .provider = DeckProvider::Deckstats};
return true; return true;
} else if ((match = rxManabox.match(url)).hasMatch()) {
QString deckPath = match.captured(1);
outInfo = ParsedDeckInfo{.baseUrl = MANABOX_BASE,
.deckID = deckPath,
.fullUrl = MANABOX_BASE + deckPath,
.provider = DeckProvider::Manabox};
return true;
} }
return false; return false;

View file

@ -15,6 +15,7 @@ enum class DeckProvider
Archidekt, Archidekt,
Moxfield, Moxfield,
Deckstats, Deckstats,
Manabox,
Unknown Unknown
}; };

View file

@ -8,12 +8,19 @@
#define INTERFACE_JSON_DECK_PARSER_H #define INTERFACE_JSON_DECK_PARSER_H
#include "../../../interface/deck_loader/card_node_function.h" #include "../../../interface/deck_loader/card_node_function.h"
#include "libcockatrice/deck_list/tree/inner_deck_list_node.h"
#include <QJsonArray> #include <QJsonArray>
#include <QJsonObject> #include <QJsonObject>
#include <QList>
#include <QLoggingCategory>
#include <QString>
#include <QTextStream>
#include <libcockatrice/card/import/card_name_normalizer.h> #include <libcockatrice/card/import/card_name_normalizer.h>
#include <libcockatrice/deck_list/deck_list.h> #include <libcockatrice/deck_list/deck_list.h>
inline Q_LOGGING_CATEGORY(InterfaceJsonDeckParserLog, "interface_json_deck_parser");
class IJsonDeckParser class IJsonDeckParser
{ {
public: public:
@ -32,9 +39,6 @@ public:
QString deckName = obj.value("name").toString(); QString deckName = obj.value("name").toString();
QString deckDescription = obj.value("description").toString(); QString deckDescription = obj.value("description").toString();
deckList.setName(deckName);
deckList.setComments(deckDescription);
QString outputText; QString outputText;
QTextStream outStream(&outputText); QTextStream outStream(&outputText);
@ -50,8 +54,14 @@ public:
outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n'; outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n';
} }
outStream.flush();
outStream.seek(0);
deckList.loadFromStream_Plain(outStream, false, CardNameNormalizer()); deckList.loadFromStream_Plain(outStream, false, CardNameNormalizer());
deckList.setName(deckName);
deckList.setComments(deckDescription);
deckList.forEachCard(CardNodeFunction::ResolveProviderId()); deckList.forEachCard(CardNodeFunction::ResolveProviderId());
deckList.setName(deckName);
deckList.setComments(deckDescription);
return deckList; return deckList;
} }
@ -67,9 +77,6 @@ public:
QString deckName = obj.value("name").toString(); QString deckName = obj.value("name").toString();
QString deckDescription = obj.value("description").toString(); QString deckDescription = obj.value("description").toString();
deckList.setName(deckName);
deckList.setComments(deckDescription);
QString outputText; QString outputText;
QTextStream outStream(&outputText); QTextStream outStream(&outputText);
@ -97,8 +104,14 @@ public:
outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n'; outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n';
} }
outStream.flush();
outStream.seek(0);
deckList.loadFromStream_Plain(outStream, false, CardNameNormalizer()); deckList.loadFromStream_Plain(outStream, false, CardNameNormalizer());
deckList.setName(deckName);
deckList.setComments(deckDescription);
deckList.forEachCard(CardNodeFunction::ResolveProviderId()); deckList.forEachCard(CardNodeFunction::ResolveProviderId());
deckList.setName(deckName);
deckList.setComments(deckDescription);
QJsonObject commandersObj = obj.value("commanders").toObject(); QJsonObject commandersObj = obj.value("commanders").toObject();
if (!commandersObj.isEmpty()) { if (!commandersObj.isEmpty()) {
@ -114,6 +127,113 @@ public:
} }
} }
// Moxfield stores Oathbreaker signature spells in their own board,
// separate from both mainboard and commanders. They are playable deck
// cards, but must not replace the Oathbreaker as the banner card.
const QJsonObject signatureSpells = obj.value("signatureSpells").toObject();
for (auto it = signatureSpells.begin(); it != signatureSpells.end(); ++it) {
const QJsonObject entry = it.value().toObject();
const QJsonObject card = entry.value("card").toObject();
const QString name = card.value("name").toString();
const int quantity = entry.value("quantity").toInt();
if (name.isEmpty() || quantity <= 0) {
continue;
}
const QString set = card.value("set").toString().toUpper();
const QString collectorNumber = card.value("cn").toString();
const QString providerId = card.value("scryfall_id").toString();
deckList.addCards(name, quantity, DECK_ZONE_MAIN, -1, set, collectorNumber, providerId);
}
return deckList;
}
};
class ManaboxJsonParser : public IJsonDeckParser
{
enum BoardType : quint32
{
MainBoard = 0,
SideBoard = 1,
Commander = 2,
MaybeBoard = 3,
};
public:
DeckList parse(const QJsonObject &obj) override
{
DeckList deckList;
QString deckName = obj.value("name").toString();
QString outputText;
QString mainboardText;
QString sideboardText;
QTextStream mainboardStream(&mainboardText);
QTextStream sideboardStream(&sideboardText);
QTextStream outStream(&outputText);
auto commanderList = QList<QJsonObject>();
auto maybeboardList = QList<QJsonObject>();
for (auto entry : obj.value("cards").toArray()) {
auto card = entry.toObject();
auto collectorNumber = card.value("collectorNumber").toString();
auto cardName = card.value("name").toString();
auto boardType = static_cast<BoardType>(card.value("boardCategory").toInt());
auto setName = card.value("setId").toString();
auto quantity = card.value("quantity").toInt();
auto providerId = card.value("scryfallId").toString();
switch (boardType) {
case MainBoard:
mainboardStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber
<< '\n';
break;
case SideBoard:
sideboardStream << "SB: " << quantity << ' ' << cardName << " (" << setName << ") "
<< collectorNumber << '\n';
break;
case MaybeBoard:
maybeboardList.append(card);
break;
case Commander:
commanderList.append(card);
break;
default:
qCWarning(InterfaceJsonDeckParserLog)
<< "Unknown ManaBox Zone" << card.value("boardCategory").toInt() << "for" << cardName;
break;
}
}
outStream << mainboardText << sideboardText;
deckList.loadFromStream_Plain(outStream, false, CardNameNormalizer());
deckList.setName(deckName);
deckList.forEachCard(CardNodeFunction::ResolveProviderId());
for (const auto &maybeboardCard : maybeboardList) {
QString cardName = maybeboardCard.value("name").toString();
QString setName = maybeboardCard.value("setId").toString().toUpper();
QString collectorNumber = maybeboardCard.value("collectorNumber").toString();
QString providerId = maybeboardCard.value("scryfallId").toString();
for (int x = 0; x < maybeboardCard.value("quantity").toInt(); x++) {
deckList.addCard(cardName, DECK_ZONE_MAYBEBOARD, -1, setName, collectorNumber, providerId);
}
}
for (const auto &commanderCard : commanderList) {
QString commanderName = commanderCard.value("name").toString();
QString setName = commanderCard.value("setId").toString().toUpper();
QString collectorNumber = commanderCard.value("collectorNumber").toString();
QString providerId = commanderCard.value("scryfallId").toString();
deckList.setBannerCard({commanderName, providerId});
deckList.addCard(commanderName, DECK_ZONE_MAIN, -1, setName, collectorNumber, providerId);
}
return deckList; return deckList;
} }
}; };

View file

@ -1,5 +1,6 @@
#include "shortcut_treeview.h" #include "shortcut_treeview.h"
#include "../../interface/theme_manager.h"
#include "cache_settings.h" #include "cache_settings.h"
#include "shortcuts_settings.h" #include "shortcuts_settings.h"
@ -78,6 +79,18 @@ ShortcutTreeView::ShortcutTreeView(QWidget *parent) : QTreeView(parent)
connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this, connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this,
&ShortcutTreeView::refreshShortcuts); &ShortcutTreeView::refreshShortcuts);
// Group headers store their font on the item; re-apply the role family on
// font changes (setFont emits itemChanged, which repaints the rows).
connect(themeManager, &ThemeManager::fontsChanged, this, [this] {
QFont groupFont = themeManager->appFont(AppFont::InterfaceDefault);
groupFont.setBold(true);
for (int row = 0; row < shortcutsModel->rowCount(); ++row) {
if (auto *groupItem = shortcutsModel->item(row)) {
groupItem->setFont(groupFont);
}
}
});
} }
void ShortcutTreeView::populateShortcutsModel() void ShortcutTreeView::populateShortcutsModel()
@ -93,7 +106,8 @@ void ShortcutTreeView::populateShortcutsModel()
curParent = parentItems.value(group); curParent = parentItems.value(group);
} else { } else {
curParent = new QStandardItem(group); curParent = new QStandardItem(group);
static QFont font = curParent->font(); // Family comes from the InterfaceDefault theme role; bold stays caller-side.
QFont font = themeManager->appFont(AppFont::InterfaceDefault);
font.setBold(true); font.setBold(true);
curParent->setFont(font); curParent->setFont(font);
parentItems.insert(group, curParent); parentItems.insert(group, curParent);

View file

@ -1,5 +1,7 @@
#include "filter_tree_model.h" #include "filter_tree_model.h"
#include "../interface/theme_manager.h"
#include <QFlags> #include <QFlags>
#include <QFont> #include <QFont>
#include <QString> #include <QString>
@ -39,6 +41,12 @@ FilterTreeModel::FilterTreeModel(QObject *parent) : QAbstractItemModel(parent)
connect(fTree, &FilterTree::postInsertRow, this, &FilterTreeModel::proxyEndInsertRow); connect(fTree, &FilterTree::postInsertRow, this, &FilterTreeModel::proxyEndInsertRow);
connect(fTree, &FilterTree::preRemoveRow, this, &FilterTreeModel::proxyBeginRemoveRow); connect(fTree, &FilterTree::preRemoveRow, this, &FilterTreeModel::proxyBeginRemoveRow);
connect(fTree, &FilterTree::postRemoveRow, this, &FilterTreeModel::proxyEndRemoveRow); connect(fTree, &FilterTree::postRemoveRow, this, &FilterTreeModel::proxyEndRemoveRow);
// FontRole data reads the DeckList role live; rebroadcast so attached
// views re-query instead of waiting for an incidental repaint.
connect(themeManager, &ThemeManager::fontsChanged, this, [this] {
emit layoutAboutToBeChanged();
emit layoutChanged();
});
} }
FilterTreeModel::~FilterTreeModel() FilterTreeModel::~FilterTreeModel()
@ -202,7 +210,8 @@ QVariant FilterTreeModel::data(const QModelIndex &index, int role) const
switch (role) { switch (role) {
case Qt::FontRole: case Qt::FontRole:
if (!node->isLeaf()) { if (!node->isLeaf()) {
QFont f; // Family comes from the DeckList theme role; bold stays caller-side.
QFont f = themeManager->appFont(AppFont::DeckList);
f.setBold(true); f.setBold(true);
return f; return f;
} }

View file

@ -3,6 +3,7 @@
#include "../../client/settings/cache_settings.h" #include "../../client/settings/cache_settings.h"
#include "../../interface/card_localization.h" #include "../../interface/card_localization.h"
#include "../../interface/card_picture_loader/card_picture_loader.h" #include "../../interface/card_picture_loader/card_picture_loader.h"
#include "../../interface/theme_manager.h"
#include "../card_dimensions.h" #include "../card_dimensions.h"
#include "../game_scene.h" #include "../game_scene.h"
#include "../z_values.h" #include "../z_values.h"
@ -154,7 +155,7 @@ void AbstractCardItem::transformPainter(QPainter *painter, const QSizeF &transla
painter->rotate(angle); painter->rotate(angle);
painter->translate(-translatedSize.width() / 2, -translatedSize.height() / 2); painter->translate(-translatedSize.width() / 2, -translatedSize.height() / 2);
QFont f; QFont f = themeManager->appFont(AppFont::CardText);
f.setPixelSize(fontSize * scale); f.setPixelSize(fontSize * scale);
painter->setFont(f); painter->setFont(f);

View file

@ -1,5 +1,7 @@
#include "abstract_graphics_item.h" #include "abstract_graphics_item.h"
#include "../../interface/theme_manager.h"
#include <QBrush> #include <QBrush>
#include <QFont> #include <QFont>
#include <QFontMetrics> #include <QFontMetrics>
@ -20,7 +22,7 @@ void AbstractGraphicsItem::paintNumberEllipse(int number,
painter->save(); painter->save();
QString numStr = QString::number(number); QString numStr = QString::number(number);
QFont font("Serif"); QFont font = themeManager->cardTextFont();
font.setPixelSize(fontSize); font.setPixelSize(fontSize);
font.setWeight(QFont::Bold); font.setWeight(QFont::Bold);

View file

@ -1,6 +1,7 @@
#include "counter_general.h" #include "counter_general.h"
#include "../../interface/pixel_map_generator.h" #include "../../interface/pixel_map_generator.h"
#include "../../interface/theme_manager.h"
#include "abstract_graphics_item.h" #include "abstract_graphics_item.h"
#include <QFont> #include <QFont>
@ -41,7 +42,7 @@ void GeneralCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /
painter->drawPixmap(QPoint(0, 0), pixmap); painter->drawPixmap(QPoint(0, 0), pixmap);
if (value) { if (value) {
QFont f("Serif"); QFont f = themeManager->cardTextFont();
f.setPixelSize(qMax((int)(radius * scaleFactor), 10)); f.setPixelSize(qMax((int)(radius * scaleFactor), 10));
f.setWeight(QFont::Bold); f.setWeight(QFont::Bold);
painter->setPen(Qt::black); painter->setPen(Qt::black);

View file

@ -240,8 +240,8 @@ void DeckViewCardContainer::paint(QPainter *painter, const QStyleOptionGraphicsI
painter->drawLine(QPointF(0, separatorY), QPointF(width, separatorY)); painter->drawLine(QPointF(0, separatorY), QPointF(width, separatorY));
painter->setPen(QColor(Qt::white)); painter->setPen(QColor(Qt::white));
QFont f("Serif"); // Family comes from the DeckList theme role; pixel sizes/weights unchanged.
f.setStyleHint(QFont::Serif); QFont f = themeManager->deckListFont();
f.setPixelSize(24); f.setPixelSize(24);
f.setWeight(QFont::Bold); f.setWeight(QFont::Bold);
painter->setFont(f); painter->setFont(f);
@ -292,8 +292,8 @@ QList<QPair<int, int>> DeckViewCardContainer::getRowsAndCols() const
int DeckViewCardContainer::getCardTypeTextWidth() const int DeckViewCardContainer::getCardTypeTextWidth() const
{ {
QFont f("Serif"); // Family comes from the DeckList theme role; pixel size/weight unchanged.
f.setStyleHint(QFont::Serif); QFont f = themeManager->deckListFont();
f.setPixelSize(16); f.setPixelSize(16);
f.setWeight(QFont::Bold); f.setWeight(QFont::Bold);
QFontMetrics fm(f); QFontMetrics fm(f);
@ -366,6 +366,15 @@ void DeckViewCardContainer::setWidth(qreal _width)
DeckViewScene::DeckViewScene(QObject *parent) : QGraphicsScene(parent), locked(true), deck(0), optimalAspectRatio(1.0) DeckViewScene::DeckViewScene(QObject *parent) : QGraphicsScene(parent), locked(true), deck(0), optimalAspectRatio(1.0)
{ {
// Zone headers read the DeckList role at paint time and cache their
// rendering, so touch every item to invalidate the caches and pick up the
// new family on font changes (containers cannot connect this themselves:
// QGraphicsItem is not a QObject).
connect(themeManager, &ThemeManager::fontsChanged, this, [this] {
for (auto *item : items()) {
item->update();
}
});
} }
DeckViewScene::~DeckViewScene() DeckViewScene::~DeckViewScene()

View file

@ -1,6 +1,7 @@
#include "game_scene.h" #include "game_scene.h"
#include "../client/settings/cache_settings.h" #include "../client/settings/cache_settings.h"
#include "../interface/theme_manager.h"
#include "animated_item.h" #include "animated_item.h"
#include "board/abstract_card_item.h" #include "board/abstract_card_item.h"
#include "board/arrow_item.h" #include "board/arrow_item.h"
@ -64,6 +65,14 @@ GameScene::GameScene(PhasesToolbar *_phasesToolbar, QObject *parent)
addItem(phasesToolbar); addItem(phasesToolbar);
connect(&SettingsCache::instance().userInterface(), &InterfaceSettings::minPlayersForMultiColumnLayoutChanged, this, connect(&SettingsCache::instance().userInterface(), &InterfaceSettings::minPlayersForMultiColumnLayoutChanged, this,
&GameScene::rearrange); &GameScene::rearrange);
// CardText items (P/T boxes, counters, player names) read the role at
// paint time and cache their rendering, so touch every item to invalidate
// the caches and pick up the new family on font changes.
connect(themeManager, &ThemeManager::fontsChanged, this, [this] {
for (auto *item : items()) {
item->update();
}
});
rearrange(); rearrange();
} }

View file

@ -2,12 +2,14 @@
#include "../client/settings/cache_settings.h" #include "../client/settings/cache_settings.h"
#include "../client/settings/shortcuts_settings.h" #include "../client/settings/shortcuts_settings.h"
#include "../interface/theme_manager.h"
#include "game_scene.h" #include "game_scene.h"
#include <QAction> #include <QAction>
#include <QBrush> #include <QBrush>
#include <QColor> #include <QColor>
#include <QEvent> #include <QEvent>
#include <QFont>
#include <QGraphicsScene> #include <QGraphicsScene>
#include <QGridLayout> #include <QGridLayout>
#include <QLabel> #include <QLabel>
@ -24,6 +26,21 @@
#include <libcockatrice/utility/qt_utils.h> #include <libcockatrice/utility/qt_utils.h>
#include <qnamespace.h> #include <qnamespace.h>
namespace
{
QFont deckOverlayFont(int pixelSize, bool bold)
{
// The drag/total counters and the tally columns are in-game overlays, not
// deck list text: keep the monospace family the stylesheets used to set so
// digit widths stay stable as counts tick and `white-space: pre` lines up.
QFont font(QStringLiteral("monospace"));
font.setStyleHint(QFont::Monospace);
font.setPixelSize(pixelSize);
font.setBold(bold);
return font;
}
} // namespace
// QRubberBand calls raise() in showEvent() and changeEvent() to stay on top of siblings. // QRubberBand calls raise() in showEvent() and changeEvent() to stay on top of siblings.
// This subclass disables that behavior so dragCountLabel can appear above it. // This subclass disables that behavior so dragCountLabel can appear above it.
class SelectionRubberBand : public QRubberBand class SelectionRubberBand : public QRubberBand
@ -75,15 +92,14 @@ GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, par
rubberBand = new SelectionRubberBand(QRubberBand::Rectangle, this); rubberBand = new SelectionRubberBand(QRubberBand::Rectangle, this);
const QString baseProperties = "color: white; " const QString baseProperties = "color: white; "
"font-family: monospace; "
"background-color: rgba(0, 0, 0, 160); " "background-color: rgba(0, 0, 0, 160); "
"border-radius: 3px; " "border-radius: 3px; "
"padding: 1px 2px; " "padding: 1px 2px; "
"white-space: pre;"; "white-space: pre;";
const QString dragCountLabelStyle = baseProperties + "font-size: 14px; font-weight: bold;"; const QString dragCountLabelStyle = baseProperties;
const QString totalCountLabelStyle = baseProperties + "font-size: 16px; font-weight: bold;"; const QString totalCountLabelStyle = baseProperties;
const QString subtypeTallyLabelStyle = baseProperties + "font-size: 12px;"; const QString subtypeTallyLabelStyle = baseProperties;
dragCountLabel = new QLabel(this); dragCountLabel = new QLabel(this);
dragCountLabel->setStyleSheet(dragCountLabelStyle); dragCountLabel->setStyleSheet(dragCountLabelStyle);
@ -100,6 +116,27 @@ GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, par
tallyLayout->setContentsMargins(2, 2, 2, 2); tallyLayout->setContentsMargins(2, 2, 2, 2);
tallyLayout->setSpacing(2); tallyLayout->setSpacing(2);
tallyContainer->hide(); tallyContainer->hide();
applyDeckOverlayFonts();
}
void GameView::applyDeckOverlayFonts()
{
dragCountLabel->setFont(deckOverlayFont(14, true));
totalCountLabel->setFont(deckOverlayFont(16, true));
tallyContainer->setFont(deckOverlayFont(12, false));
for (int i = 0; i < tallyLayout->count(); ++i) {
if (auto *label = qobject_cast<QLabel *>(tallyLayout->itemAt(i)->widget())) {
// Count column (odd positions) keeps the bold emphasis.
const bool count = i % 2 == 1;
label->setFont(deckOverlayFont(count ? 14 : 12, count));
}
}
// The container keeps the size computed from the previous fonts, so force
// rebuildTallyLabels() to re-measure it instead of reusing cachedTallyRows.
cachedTallyRows.clear();
updateTotalSelectionCount();
} }
void GameView::resizeEvent(QResizeEvent *event) void GameView::resizeEvent(QResizeEvent *event)
@ -219,9 +256,8 @@ QSize GameView::rebuildTallyLabels(const QList<TallyRow> &entries)
{ {
clearTallyLabels(); clearTallyLabels();
const QString nameStyle = QStringLiteral("color: white; font-size: 12px; background: transparent;"); const QString nameStyle = QStringLiteral("color: white; background: transparent;");
const QString countStyle = const QString countStyle = QStringLiteral("color: white; background: transparent;");
QStringLiteral("color: white; font-size: 14px; font-weight: bold; background: transparent;");
int totalHeight = 0; int totalHeight = 0;
int maxNameWidth = 0; int maxNameWidth = 0;
@ -231,11 +267,13 @@ QSize GameView::rebuildTallyLabels(const QList<TallyRow> &entries)
for (const TallyRow &entry : entries) { for (const TallyRow &entry : entries) {
auto *nameLabel = new QLabel(entry.name, tallyContainer); auto *nameLabel = new QLabel(entry.name, tallyContainer);
nameLabel->setStyleSheet(nameStyle); nameLabel->setStyleSheet(nameStyle);
nameLabel->setFont(deckOverlayFont(12, false));
nameLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); nameLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
tallyLayout->addWidget(nameLabel, row, 0); tallyLayout->addWidget(nameLabel, row, 0);
auto *countLabel = new QLabel(entry.value, tallyContainer); auto *countLabel = new QLabel(entry.value, tallyContainer);
countLabel->setStyleSheet(countStyle); countLabel->setStyleSheet(countStyle);
countLabel->setFont(deckOverlayFont(14, true));
countLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); countLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
tallyLayout->addWidget(countLabel, row, 1); tallyLayout->addWidget(countLabel, row, 1);

View file

@ -50,6 +50,7 @@ private slots:
void refreshShortcuts(); void refreshShortcuts();
void updateTotalSelectionCount(const QSize &viewSize = QSize()); void updateTotalSelectionCount(const QSize &viewSize = QSize());
void setFocusDisabled(bool disabled); void setFocusDisabled(bool disabled);
void applyDeckOverlayFonts();
public slots: public slots:
void updateSceneRect(const QRectF &rect); void updateSceneRect(const QRectF &rect);

View file

@ -2,6 +2,7 @@
#include "../../client/settings/cache_settings.h" #include "../../client/settings/cache_settings.h"
#include "../../interface/pixel_map_generator.h" #include "../../interface/pixel_map_generator.h"
#include "../../interface/theme_manager.h"
#include "../board/abstract_counter.h" #include "../board/abstract_counter.h"
#include "../board/abstract_graphics_item.h" #include "../board/abstract_graphics_item.h"
#include "../board/arrow_target.h" #include "../board/arrow_target.h"
@ -76,7 +77,7 @@ void PlayerCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*
QRectF translatedRect = path.controlPointRect(); QRectF translatedRect = path.controlPointRect();
QSize translatedSize = translatedRect.size().toSize(); QSize translatedSize = translatedRect.size().toSize();
QFont font("Serif"); QFont font = themeManager->cardTextFont();
font.setWeight(QFont::Bold); font.setWeight(QFont::Bold);
font.setPixelSize(qMax(qRound(translatedSize.height() / 1.3), 9)); font.setPixelSize(qMax(qRound(translatedSize.height() / 1.3), 9));
painter->setFont(font); painter->setFont(font);
@ -226,7 +227,7 @@ void PlayerTarget::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*o
name = name.mid(0, 10) + "..."; name = name.mid(0, 10) + "...";
} }
QFont font; QFont font = themeManager->appFont(AppFont::CardText);
font.setPixelSize(qMax(qRound(translatedNameRect.height() / 1.5), 9)); font.setPixelSize(qMax(qRound(translatedNameRect.height() / 1.5), 9));
painter->setFont(font); painter->setFont(font);
painter->setPen(Qt::white); painter->setPen(Qt::white);

View file

@ -11,6 +11,7 @@
#include <QSharedPointer> #include <QSharedPointer>
#include <QTimer> #include <QTimer>
#include <QUrl> #include <QUrl>
#include <QtGlobal>
class ExactCard; class ExactCard;
@ -38,32 +39,94 @@ void CardPictureLoaderStatusBar::cleanOldEntries()
if (!loadLog || !loadLog->popup) { if (!loadLog || !loadLog->popup) {
return; return;
} }
for (CardPictureLoaderRequestStatusDisplayWidget *statusDisplayWidget :
loadLog->popup->findChildren<CardPictureLoaderRequestStatusDisplayWidget *>()) { const QDateTime now = QDateTime::currentDateTime();
// Removing an entry mutates the log, so walk a copy of it.
const QList<CardPictureLoaderRequestStatusDisplayWidget *> entries = logEntries;
for (CardPictureLoaderRequestStatusDisplayWidget *statusDisplayWidget : entries) {
statusDisplayWidget->queryElapsedSeconds(); statusDisplayWidget->queryElapsedSeconds();
if (statusDisplayWidget->getFinished() && if (!statusDisplayWidget->getFinished()) {
QDateTime::fromString(statusDisplayWidget->getStartTime()).secsTo(QDateTime::currentDateTime()) > 10) { continue;
loadLog->removeSettingsWidget(statusDisplayWidget); }
progressBar->setMaximum(progressBar->maximum() - 1); if (QDateTime::fromString(statusDisplayWidget->getStartTime()).secsTo(now) > LOG_ENTRY_LIFETIME_SECS) {
progressBar->setValue(progressBar->value() - 1); removeLogEntry(statusDisplayWidget);
} }
} }
} }
void CardPictureLoaderStatusBar::addQueuedImageLoad(const QUrl &url, const ExactCard &card, const QString &setName) void CardPictureLoaderStatusBar::addQueuedImageLoad(const QUrl &url, const ExactCard &card, const QString &setName)
{ {
loadLog->addSettingsWidget(new CardPictureLoaderRequestStatusDisplayWidget(loadLog, url, card, setName)); if (logEntries.size() >= MAX_LOG_ENTRIES) {
trimLog();
}
auto *entry = new CardPictureLoaderRequestStatusDisplayWidget(loadLog, url, card, setName);
loadLog->addSettingsWidget(entry);
logEntries.append(entry);
entriesByUrl.insert(entry->getUrl(), entry);
progressBar->setMaximum(progressBar->maximum() + 1); progressBar->setMaximum(progressBar->maximum() + 1);
} }
void CardPictureLoaderStatusBar::addSuccessfulImageLoad(const QUrl &url) void CardPictureLoaderStatusBar::addSuccessfulImageLoad(const QUrl &url)
{ {
progressBar->setValue(progressBar->value() + 1); const QList<CardPictureLoaderRequestStatusDisplayWidget *> entries = entriesByUrl.values(url.toString());
for (CardPictureLoaderRequestStatusDisplayWidget *statusDisplayWidget : if (entries.isEmpty()) {
loadLog->popup->findChildren<CardPictureLoaderRequestStatusDisplayWidget *>()) { // The entry may already have been dropped from the log; the download
if (statusDisplayWidget->getUrl() == url.toString()) { // itself still succeeded, there is just nothing left to mark.
statusDisplayWidget->queryElapsedSeconds(); return;
statusDisplayWidget->setFinished(); }
int finishedEntries = 0;
for (CardPictureLoaderRequestStatusDisplayWidget *statusDisplayWidget : entries) {
if (statusDisplayWidget->getFinished()) {
continue;
}
statusDisplayWidget->queryElapsedSeconds();
statusDisplayWidget->setFinished();
++finishedEntries;
}
// The bar counts the entries currently in the log, so only entries that are
// still listed move it.
progressBar->setValue(progressBar->value() + finishedEntries);
}
void CardPictureLoaderStatusBar::trimLog()
{
if (logEntries.isEmpty()) {
return;
}
// Prefer evicting an entry that has already finished; only fall back to an
// in-flight request when the whole log is still pending.
CardPictureLoaderRequestStatusDisplayWidget *toRemove = nullptr;
for (CardPictureLoaderRequestStatusDisplayWidget *entry : logEntries) {
if (entry->getFinished()) {
toRemove = entry;
break;
} }
} }
}
removeLogEntry(toRemove != nullptr ? toRemove : logEntries.constFirst());
}
void CardPictureLoaderStatusBar::removeLogEntry(CardPictureLoaderRequestStatusDisplayWidget *entry)
{
if (entry == nullptr) {
return;
}
logEntries.removeOne(entry);
entriesByUrl.remove(entry->getUrl(), entry);
// The progress bar's maximum counts the entries currently in the log and
// its value the finished ones, so dropping an entry moves both counters back.
// Move the value first: a maximum of 0 turns the bar into a busy indicator and
// resets its value.
if (entry->getFinished()) {
progressBar->setValue(qMax(0, progressBar->value() - 1));
}
progressBar->setMaximum(qMax(0, progressBar->maximum() - 1));
loadLog->removeSettingsWidget(entry);
}

View file

@ -1,10 +1,13 @@
#ifndef PICTURE_LOADER_STATUS_BAR_H #ifndef PICTURE_LOADER_STATUS_BAR_H
#define PICTURE_LOADER_STATUS_BAR_H #define PICTURE_LOADER_STATUS_BAR_H
#include <QList>
#include <QMultiHash>
#include <QString> #include <QString>
#include <QWidget> #include <QWidget>
#include <qtmetamacros.h> #include <qtmetamacros.h>
class CardPictureLoaderRequestStatusDisplayWidget;
class ExactCard; class ExactCard;
class QHBoxLayout; class QHBoxLayout;
class QProgressBar; class QProgressBar;
@ -21,11 +24,20 @@ class SettingsButtonWidget;
* - Shows overall progress of image downloads. * - Shows overall progress of image downloads.
* - Maintains a log of individual requests via a popup (SettingsButtonWidget). * - Maintains a log of individual requests via a popup (SettingsButtonWidget).
* - Cleans up finished request entries automatically after a delay. * - Cleans up finished request entries automatically after a delay.
*
* The log is a diagnostic view, not a queue: it keeps at most MAX_LOG_ENTRIES
* entries and indexes them by URL, so a burst of requests stays O(1) per event
* instead of scanning every entry the popup holds.
*/ */
class CardPictureLoaderStatusBar : public QWidget class CardPictureLoaderStatusBar : public QWidget
{ {
Q_OBJECT Q_OBJECT
public: public:
/** Entries kept in the log; older ones are evicted as new requests arrive. */
static constexpr int MAX_LOG_ENTRIES = 50;
/** Seconds a finished entry stays in the log before it is cleaned up. */
static constexpr int LOG_ENTRY_LIFETIME_SECS = 10;
/** /**
* @brief Constructs a status bar with progress bar and log button. * @brief Constructs a status bar with progress bar and log button.
* @param parent Parent widget * @param parent Parent widget
@ -55,10 +67,28 @@ public slots:
void cleanOldEntries(); void cleanOldEntries();
private: private:
/**
* @brief Evicts the oldest finished entry (or the oldest entry if none finished)
* until the log fits within MAX_LOG_ENTRIES.
*/
void trimLog();
/**
* @brief Removes one entry from the log, its URL index and the popup, and keeps
* the progress bar counters in step with the entries that remain.
* @param entry Entry to remove
*/
void removeLogEntry(CardPictureLoaderRequestStatusDisplayWidget *entry);
QHBoxLayout *layout; ///< Horizontal layout containing progress bar and log button QHBoxLayout *layout; ///< Horizontal layout containing progress bar and log button
QProgressBar *progressBar; ///< Progress bar showing overall download progress QProgressBar *progressBar; ///< Progress bar showing overall download progress
SettingsButtonWidget *loadLog; ///< Popup log showing individual request statuses SettingsButtonWidget *loadLog; ///< Popup log showing individual request statuses
QTimer *cleaner; ///< Timer for periodically cleaning old log entries QTimer *cleaner; ///< Timer for periodically cleaning old log entries
QList<CardPictureLoaderRequestStatusDisplayWidget *> logEntries; ///< Logged requests, oldest first
// A URL can back several live entries: a request parked for a deferred retry is re-queued while
// its first entry is still pending, so a completion must finish every entry for that URL.
QMultiHash<QString, CardPictureLoaderRequestStatusDisplayWidget *>
entriesByUrl; ///< Log entries indexed by their URL for O(1) completion lookup
}; };
#endif // PICTURE_LOADER_STATUS_BAR_H #endif // PICTURE_LOADER_STATUS_BAR_H

View file

@ -1,5 +1,6 @@
#include "deck_loader.h" #include "deck_loader.h"
#include "../theme_manager.h"
#include "libcockatrice/card/card_info.h" #include "libcockatrice/card/card_info.h"
#include "libcockatrice/card/database/card_database_querier.h" #include "libcockatrice/card/database/card_database_querier.h"
#include "libcockatrice/deck_list/tree/abstract_deck_list_card_node.h" #include "libcockatrice/deck_list/tree/abstract_deck_list_card_node.h"
@ -579,6 +580,8 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode
if (!cards.isEmpty()) { if (!cards.isEmpty()) {
QTextBlockFormat blockFormat; QTextBlockFormat blockFormat;
QTextCharFormat charFormat; QTextCharFormat charFormat;
// Family comes from the DeckList theme role; sizes/weights unchanged.
charFormat.setFontFamilies({themeManager->deckListFont().family()});
charFormat.setFontPointSize(11); charFormat.setFontPointSize(11);
charFormat.setFontWeight(QFont::Bold); charFormat.setFontWeight(QFont::Bold);
cursor->insertBlock(blockFormat, charFormat); cursor->insertBlock(blockFormat, charFormat);
@ -592,6 +595,7 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode
const AbstractDecklistCardNode *card = cards[i]; const AbstractDecklistCardNode *card = cards[i];
QTextCharFormat cellCharFormat; QTextCharFormat cellCharFormat;
cellCharFormat.setFontFamilies({themeManager->deckListFont().family()});
cellCharFormat.setFontPointSize(9); cellCharFormat.setFontPointSize(9);
QTextTableCell cell = table->cellAt(i, 0); QTextTableCell cell = table->cellAt(i, 0);
@ -613,6 +617,7 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode
QTextBlockFormat blockFormat; QTextBlockFormat blockFormat;
QTextCharFormat charFormat; QTextCharFormat charFormat;
charFormat.setFontFamilies({themeManager->deckListFont().family()});
charFormat.setFontPointSize(14); charFormat.setFontPointSize(14);
charFormat.setFontWeight(QFont::Bold); charFormat.setFontWeight(QFont::Bold);
@ -640,14 +645,18 @@ void DeckLoader::printDeckList(QPrinter *printer, const DeckList &deckList)
{ {
QTextDocument doc; QTextDocument doc;
QFont font("Serif"); // Family comes from the DeckList theme role; the print sizes/weights below stay unchanged.
font.setStyleHint(QFont::Serif); // Print output is a one-shot snapshot and intentionally does not subscribe
// to fontsChanged (there is no live view to repaint); reprinting picks up
// the current role font.
QFont font = themeManager->deckListFont();
doc.setDefaultFont(font); doc.setDefaultFont(font);
QTextCursor cursor(&doc); QTextCursor cursor(&doc);
QTextBlockFormat headerBlockFormat; QTextBlockFormat headerBlockFormat;
QTextCharFormat headerCharFormat; QTextCharFormat headerCharFormat;
headerCharFormat.setFontFamilies({themeManager->deckListFont().family()});
headerCharFormat.setFontPointSize(16); headerCharFormat.setFontPointSize(16);
headerCharFormat.setFontWeight(QFont::Bold); headerCharFormat.setFontWeight(QFont::Bold);

View file

@ -1,5 +1,7 @@
#include "color_button.h" #include "color_button.h"
#include "../theme_manager.h"
#include <QBrush> #include <QBrush>
#include <QColorDialog> #include <QColorDialog>
#include <QFont> #include <QFont>
@ -21,6 +23,7 @@ ColorButton::ColorButton(QWidget *parent) : QToolButton(parent)
setCursor(Qt::PointingHandCursor); setCursor(Qt::PointingHandCursor);
setToolTip(tr("Click to pick a color")); setToolTip(tr("Click to pick a color"));
connect(this, &QToolButton::clicked, this, &ColorButton::pickColor); connect(this, &QToolButton::clicked, this, &ColorButton::pickColor);
connect(themeManager, &ThemeManager::fontsChanged, this, &ColorButton::updateSwatch);
} }
void ColorButton::setColor(const QColor &c) void ColorButton::setColor(const QColor &c)
@ -73,7 +76,8 @@ void ColorButton::updateSwatch()
// Hex label — black or white for contrast // Hex label — black or white for contrast
int luma = 0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue(); int luma = 0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue();
painter.setPen((luma > 128 && color.alpha() > 80) ? QColor(0, 0, 0, 180) : QColor(255, 255, 255, 200)); painter.setPen((luma > 128 && color.alpha() > 80) ? QColor(0, 0, 0, 180) : QColor(255, 255, 255, 200));
QFont f = font(); // Family comes from the InterfaceDefault theme role; the 8px size stays caller-side.
QFont f = themeManager->appFont(AppFont::InterfaceDefault);
f.setPixelSize(8); f.setPixelSize(8);
painter.setFont(f); painter.setFont(f);
painter.drawText(rect(), Qt::AlignCenter, color.name().toUpper()); painter.drawText(rect(), Qt::AlignCenter, color.name().toUpper());

View file

@ -0,0 +1,136 @@
#include "font_grid_widget.h"
#include "../theme_manager.h"
#include <QApplication>
#include <QFont>
#include <QFontComboBox>
#include <QGridLayout>
#include <QLabel>
#include <QMetaEnum>
#include <QSpinBox>
#include <QtGlobal>
static constexpr int MIN_FONT_SIZE = 6;
static constexpr int MAX_FONT_SIZE = 72;
static constexpr int FALLBACK_FONT_SIZE = 10;
static const QMap<AppFont::Role, const char *> APP_FONT_DESCRIPTIONS = {
{AppFont::InterfaceDefault, QT_TR_NOOP("Default font for menus, dialogs and widgets")},
{AppFont::ChatLog, QT_TR_NOOP("Font for chat messages and logs")},
{AppFont::CardText, QT_TR_NOOP("Font for text rendered on cards")},
{AppFont::DeckList, QT_TR_NOOP("Font for deck lists and the deck editor")},
};
static QString roleDisplayName(AppFont::Role role)
{
switch (role) {
case AppFont::InterfaceDefault:
return FontGridWidget::tr("Interface default");
case AppFont::ChatLog:
return FontGridWidget::tr("Chat log");
case AppFont::CardText:
return FontGridWidget::tr("Card text");
case AppFont::DeckList:
return FontGridWidget::tr("Deck list");
default:
return QString();
}
}
FontGridWidget::FontGridWidget(QWidget *parent) : QWidget(parent)
{
auto *grid = new QGridLayout(this);
grid->setSpacing(3);
grid->setContentsMargins(12, 8, 12, 8);
grid->setColumnStretch(0, 0);
grid->setColumnStretch(1, 1);
grid->setColumnStretch(2, 0);
QMetaEnum fontEnum = QMetaEnum::fromType<AppFont::Role>();
for (int i = 0; i < fontEnum.keyCount(); ++i) {
auto role = static_cast<AppFont::Role>(fontEnum.value(i));
auto *label = new QLabel(this);
grid->addWidget(label, i, 0);
auto *familyCombo = new QFontComboBox(this);
grid->addWidget(familyCombo, i, 1);
connect(familyCombo, &QFontComboBox::currentFontChanged, this, [this] { emit fontsChanged(); });
auto *sizeSpin = new QSpinBox(this);
sizeSpin->setRange(MIN_FONT_SIZE, MAX_FONT_SIZE);
grid->addWidget(sizeSpin, i, 2);
connect(sizeSpin, &QSpinBox::valueChanged, this, [this] { emit fontsChanged(); });
label->setBuddy(familyCombo);
fontRows[role] = {label, familyCombo, sizeSpin};
}
retranslateUi();
}
void FontGridWidget::loadFonts(const FontConfig &cfg)
{
// Suppress fontsChanged while materializing fallback rows so the initial
// load never reads as a user edit (mirrors paletteGrid blocking).
blockSignals(true);
QMetaEnum fontEnum = QMetaEnum::fromType<AppFont::Role>();
for (int i = 0; i < fontEnum.keyCount(); ++i) {
auto role = static_cast<AppFont::Role>(fontEnum.value(i));
const FontRow &row = fontRows[role];
// Roles absent from the file resolve the same way the runtime does: an unset
// role falls back to the config's InterfaceDefault, and only then to the
// startup font. appFont() would return the live preview instead, so Reset and
// a first load must not measure against it.
const QFont fallback = cfg.fonts.value(AppFont::InterfaceDefault, themeManager->defaultAppFont(role));
const QFont font = cfg.fonts.value(role, fallback);
row.familyCombo->setCurrentFont(font);
int size = font.pointSize();
if (size <= 0) {
size = qApp->font().pointSize();
}
if (size <= 0) {
size = FALLBACK_FONT_SIZE;
}
row.sizeSpin->setValue(qBound(MIN_FONT_SIZE, size, MAX_FONT_SIZE));
}
blockSignals(false);
}
FontConfig FontGridWidget::currentFontConfig() const
{
FontConfig cfg;
QMetaEnum fontEnum = QMetaEnum::fromType<AppFont::Role>();
for (int i = 0; i < fontEnum.keyCount(); ++i) {
auto role = static_cast<AppFont::Role>(fontEnum.value(i));
const FontRow &row = fontRows.value(role);
// Canonical family + point size only: a fresh QFont carries default
// (non-bold, non-italic) weight/style, so nothing combo-side leaks in.
QFont font(row.familyCombo->currentFont().family());
font.setPointSize(row.sizeSpin->value());
cfg.fonts[role] = font;
}
return cfg;
}
void FontGridWidget::retranslateUi()
{
QMetaEnum fontEnum = QMetaEnum::fromType<AppFont::Role>();
for (int i = 0; i < fontEnum.keyCount(); ++i) {
auto role = static_cast<AppFont::Role>(fontEnum.value(i));
const FontRow &row = fontRows.value(role);
row.label->setText(roleDisplayName(role));
row.label->setToolTip(tr(APP_FONT_DESCRIPTIONS.value(role, "")));
row.familyCombo->setToolTip(tr(APP_FONT_DESCRIPTIONS.value(role, "")));
row.familyCombo->setAccessibleName(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->setSuffix(tr(" pt"));
}
}

View file

@ -0,0 +1,43 @@
#ifndef COCKATRICE_FONT_GRID_WIDGET_H
#define COCKATRICE_FONT_GRID_WIDGET_H
#include "../theme_config.h"
#include <QMap>
#include <QWidget>
#include <qtmetamacros.h>
class QFontComboBox;
class QGridLayout;
class QLabel;
class QSpinBox;
// Per-role font editors — one (family, point size) row per AppFont::Role.
// Owns the load/read round-trip for FontConfig but has no file I/O, mirroring
// PaletteGridWidget. Only family and point size are edited; weight and italic
// stay caller-side concerns and are stripped when reading back.
class FontGridWidget : public QWidget
{
Q_OBJECT
public:
explicit FontGridWidget(QWidget *parent = nullptr);
void loadFonts(const FontConfig &cfg);
FontConfig currentFontConfig() const;
void retranslateUi();
signals:
void fontsChanged();
private:
struct FontRow
{
QLabel *label = nullptr;
QFontComboBox *familyCombo = nullptr;
QSpinBox *sizeSpin = nullptr;
};
QMap<AppFont::Role, FontRow> fontRows;
};
#endif // COCKATRICE_FONT_GRID_WIDGET_H

View file

@ -2,6 +2,7 @@
#include "../theme_config.h" #include "../theme_config.h"
#include "../theme_manager.h" #include "../theme_manager.h"
#include "font_grid_widget.h"
#include "palette_generator.h" #include "palette_generator.h"
#include "palette_grid_widget.h" #include "palette_grid_widget.h"
#include "quick_setup_panel.h" #include "quick_setup_panel.h"
@ -41,6 +42,7 @@ PaletteEditorDialog::PaletteEditorDialog(const QString &_themeDirPath, const QSt
// Load both scheme configs upfront so switching is instant // Load both scheme configs upfront so switching is instant
loadSchemes(); loadSchemes();
loadFonts();
loadedScheme = themeManager->isDarkMode(themeDirPath) ? "Dark" : "Light"; loadedScheme = themeManager->isDarkMode(themeDirPath) ? "Dark" : "Light";
@ -125,6 +127,33 @@ void PaletteEditorDialog::setupUi()
paletteGrid->setVisible(false); paletteGrid->setVisible(false);
root->addWidget(paletteGrid, 1); root->addWidget(paletteGrid, 1);
// Font section — sibling to the palette grid, sharing its collapsible
// advanced area pattern. Fonts are shared per theme, not split by scheme.
fontToggleButton = new QPushButton(this);
fontToggleButton->setCheckable(true);
fontToggleButton->setChecked(false);
fontToggleButton->setFlat(true);
fontToggleButton->setStyleSheet("QPushButton { text-align: left; padding: 5px 12px; font-weight: bold; }"
"QPushButton:checked { }");
root->addWidget(fontToggleButton);
fontSeparator = makeSeparator();
fontSeparator->setVisible(false);
root->addWidget(fontSeparator);
fontSection = new QWidget;
auto *fontSectionLayout = new QVBoxLayout(fontSection);
fontSectionLayout->setContentsMargins(0, 0, 0, 0);
fontSectionLayout->setSpacing(0);
fontNoteLabel = new QLabel(fontSection);
fontNoteLabel->setContentsMargins(12, 4, 12, 0);
fontNoteLabel->setWordWrap(true);
fontSectionLayout->addWidget(fontNoteLabel);
fontGrid = new FontGridWidget(fontSection);
fontSectionLayout->addWidget(fontGrid);
fontSection->setVisible(false);
root->addWidget(fontSection);
// Footer // Footer
root->addWidget(makeSeparator()); root->addWidget(makeSeparator());
footer = new QWidget; footer = new QWidget;
@ -157,6 +186,10 @@ void PaletteEditorDialog::setupUi()
connect(autoApplyTimer, &QTimer::timeout, this, &PaletteEditorDialog::onApply); connect(autoApplyTimer, &QTimer::timeout, this, &PaletteEditorDialog::onApply);
connect(quickSetupPanel, &QuickSetupPanel::valueChanged, this, &PaletteEditorDialog::onGenerateFromAccent); connect(quickSetupPanel, &QuickSetupPanel::valueChanged, this, &PaletteEditorDialog::onGenerateFromAccent);
connect(paletteGrid, &PaletteGridWidget::paletteChanged, this, [this] { autoApplyTimer->start(); }); connect(paletteGrid, &PaletteGridWidget::paletteChanged, this, [this] { autoApplyTimer->start(); });
connect(fontGrid, &FontGridWidget::fontsChanged, this, [this] {
fontsDirty = true;
autoApplyTimer->start();
});
connect(revertButton, &QPushButton::clicked, this, &PaletteEditorDialog::onRevertToDefault); connect(revertButton, &QPushButton::clicked, this, &PaletteEditorDialog::onRevertToDefault);
connect(resetBtn, &QPushButton::clicked, this, &PaletteEditorDialog::onReset); connect(resetBtn, &QPushButton::clicked, this, &PaletteEditorDialog::onReset);
connect(saveBtn, &QPushButton::clicked, this, &PaletteEditorDialog::onSave); connect(saveBtn, &QPushButton::clicked, this, &PaletteEditorDialog::onSave);
@ -166,15 +199,32 @@ void PaletteEditorDialog::setupUi()
paletteGridToggleButton->setText(open ? tr("▼ Edit Palette") : tr("▶ Edit Palette")); paletteGridToggleButton->setText(open ? tr("▼ Edit Palette") : tr("▶ Edit Palette"));
paletteGridSeparator->setVisible(open); paletteGridSeparator->setVisible(open);
paletteGrid->setVisible(open); paletteGrid->setVisible(open);
updateAdvancedHeight();
if (open) {
setMinimumHeight(680);
resize(width(), 680);
} else {
setMinimumHeight(220);
adjustSize(); // shrinks to fit just the visible content
}
}); });
connect(fontToggleButton, &QPushButton::toggled, this, [this](bool open) {
fontToggleButton->setText(open ? tr("▼ Edit Fonts") : tr("▶ Edit Fonts"));
fontSeparator->setVisible(open);
fontSection->setVisible(open);
updateAdvancedHeight();
});
}
void PaletteEditorDialog::updateAdvancedHeight()
{
int height = 220;
if (paletteGridToggleButton->isChecked()) {
height += 460;
}
if (fontToggleButton->isChecked()) {
height += 200;
}
setMinimumHeight(height);
if (paletteGrid->isVisible() || fontSection->isVisible()) {
resize(width(), height);
} else {
adjustSize(); // shrinks to fit just the visible content
}
} }
void PaletteEditorDialog::retranslateUi() void PaletteEditorDialog::retranslateUi()
@ -189,12 +239,16 @@ void PaletteEditorDialog::retranslateUi()
editingLabel->setText(tr("Editing:")); editingLabel->setText(tr("Editing:"));
paletteGridToggleButton->setText(tr("▶ Edit Palette")); paletteGridToggleButton->setText(tr("▶ Edit Palette"));
paletteGridToggleButton->setToolTip(tr("Show or hide the per-role colour grid for manual tweaks")); paletteGridToggleButton->setToolTip(tr("Show or hide the per-role colour grid for manual tweaks"));
fontToggleButton->setText(fontSection->isVisible() ? tr("▼ Edit Fonts") : tr("▶ Edit Fonts"));
fontToggleButton->setToolTip(tr("Show or hide the per-purpose font settings for this theme"));
fontNoteLabel->setText(tr("Fonts belong to theme %1 (shared by Light and Dark schemes)").arg(themeName));
fontGrid->retranslateUi();
revertButton->setText(tr("↺ Revert to theme default")); revertButton->setText(tr("↺ Revert to theme default"));
resetBtn->setText(tr("Reset")); resetBtn->setText(tr("Reset"));
saveBtn->setText(tr("Save")); saveBtn->setText(tr("Save"));
resetBtn->setToolTip(tr("Discard unsaved edits and restore the last saved palette")); resetBtn->setToolTip(tr("Discard unsaved edits and restore the last saved palette and fonts"));
saveBtn->setToolTip(tr("Write palette-%1.toml and reload the theme").arg(loadedScheme.toLower())); saveBtn->setToolTip(tr("Write palette-%1.toml and fonts.toml, then reload the theme").arg(loadedScheme.toLower()));
if (saveDir.isEmpty() || !ThemeManager::isDirReallyWritable(saveDir)) { if (saveDir.isEmpty() || !ThemeManager::isDirReallyWritable(saveDir)) {
saveBtn->setEnabled(false); saveBtn->setEnabled(false);
@ -228,6 +282,13 @@ void PaletteEditorDialog::loadSchemes()
} }
} }
void PaletteEditorDialog::loadFonts()
{
savedFontConfig = ThemeManager::loadDefaultFontConfig(themeDirPath, themeName);
fontGrid->loadFonts(savedFontConfig);
fontsDirty = false;
}
void PaletteEditorDialog::seedAccentFromScheme(const QString &scheme) void PaletteEditorDialog::seedAccentFromScheme(const QString &scheme)
{ {
QColor seed = workingConfig.value(scheme).colors.value(QPalette::Active).value(QPalette::Highlight); QColor seed = workingConfig.value(scheme).colors.value(QPalette::Active).value(QPalette::Highlight);
@ -258,6 +319,9 @@ void PaletteEditorDialog::onGenerateFromAccent(const QColor &accent, int intensi
void PaletteEditorDialog::onApply() void PaletteEditorDialog::onApply()
{ {
themeManager->previewPalette(paletteGrid->currentPaletteConfig(), loadedScheme); themeManager->previewPalette(paletteGrid->currentPaletteConfig(), loadedScheme);
if (fontsDirty) {
themeManager->previewFonts(fontGrid->currentFontConfig());
}
} }
void PaletteEditorDialog::onSave() void PaletteEditorDialog::onSave()
@ -308,6 +372,20 @@ void PaletteEditorDialog::onSave()
savedConfig[it.key()] = it.value(); savedConfig[it.key()] = it.value();
} }
// Persist the shared per-theme fonts when they changed.
if (fontsDirty) {
const FontConfig fontCfg = fontGrid->currentFontConfig();
if (fontCfg != savedFontConfig) {
if (!ThemeManager::saveFontConfig(saveDir, fontCfg)) {
QMessageBox::warning(this, tr("Save failed"),
tr("Could not write %1 to:\n%2").arg(FontConfig::fileName(), saveDir));
return;
}
savedFontConfig = fontCfg;
}
fontsDirty = false;
}
themeManager->reloadCurrentTheme(); themeManager->reloadCurrentTheme();
accept(); accept();
} }
@ -316,6 +394,12 @@ void PaletteEditorDialog::onReset()
{ {
workingConfig[loadedScheme] = savedConfig[loadedScheme]; workingConfig[loadedScheme] = savedConfig[loadedScheme];
paletteGrid->loadPalette(savedConfig[loadedScheme]); paletteGrid->loadPalette(savedConfig[loadedScheme]);
// Restore the live theme before re-reading the grid: loadFonts resolves
// unset roles, and the preview has currentFonts pointing at the edited
// config until the theme is reloaded.
themeManager->reloadCurrentTheme();
fontGrid->loadFonts(savedFontConfig);
fontsDirty = false;
seedAccentFromScheme(loadedScheme); seedAccentFromScheme(loadedScheme);
} }
@ -350,6 +434,27 @@ void PaletteEditorDialog::onRevertToDefault()
workingConfig[loadedScheme] = def; workingConfig[loadedScheme] = def;
paletteGrid->loadPalette(def); paletteGrid->loadPalette(def);
seedAccentFromScheme(loadedScheme); seedAccentFromScheme(loadedScheme);
// Revert the shared per-theme fonts the same way: delete the user-written
// fonts.toml so the theme falls back to its shipped default, then reflect
// the resolved fonts in the editor.
QFile::remove(QDir(saveDir).absoluteFilePath(FontConfig::fileName()));
// Reload the live theme so the font revert takes effect immediately.
themeManager->reloadCurrentTheme();
const FontConfig fontDef = ThemeManager::loadDefaultFontConfig(themeDirPath, themeName);
savedFontConfig = fontDef;
fontGrid->loadFonts(fontDef);
fontsDirty = false;
}
void PaletteEditorDialog::reject()
{
// Discard any unsaved live preview (palette and fonts) so closing without
// Save restores the active theme instead of leaking the preview globally.
themeManager->reloadCurrentTheme();
QDialog::reject();
} }
void PaletteEditorDialog::changeEvent(QEvent *e) void PaletteEditorDialog::changeEvent(QEvent *e)

View file

@ -15,6 +15,7 @@ class QComboBox;
class QDialogButtonBox; class QDialogButtonBox;
class QPushButton; class QPushButton;
class PaletteGridWidget; class PaletteGridWidget;
class FontGridWidget;
class QuickSetupPanel; class QuickSetupPanel;
class QFrame; class QFrame;
class QWidget; class QWidget;
@ -25,6 +26,7 @@ class PaletteEditorDialog : public QDialog
public: public:
explicit PaletteEditorDialog(const QString &themeDirPath, const QString &themeName, QWidget *parent = nullptr); explicit PaletteEditorDialog(const QString &themeDirPath, const QString &themeName, QWidget *parent = nullptr);
void loadSchemes(); void loadSchemes();
void reject() override;
private slots: private slots:
void onSave(); void onSave();
@ -38,8 +40,10 @@ private:
void setupUi(); void setupUi();
void retranslateUi(); void retranslateUi();
void refreshChromePalettes(); void refreshChromePalettes();
void updateAdvancedHeight();
void loadScheme(const QString &scheme); // snapshot current, switch, load void loadScheme(const QString &scheme); // snapshot current, switch, load
void seedAccentFromScheme(const QString &scheme); void seedAccentFromScheme(const QString &scheme);
void loadFonts(); // shared per theme, not split by light/dark
// Sub-widgets // Sub-widgets
QWidget *header; QWidget *header;
@ -49,6 +53,11 @@ private:
PaletteGridWidget *paletteGrid = nullptr; PaletteGridWidget *paletteGrid = nullptr;
QPushButton *paletteGridToggleButton = nullptr; QPushButton *paletteGridToggleButton = nullptr;
QFrame *paletteGridSeparator = nullptr; QFrame *paletteGridSeparator = nullptr;
FontGridWidget *fontGrid = nullptr;
QPushButton *fontToggleButton = nullptr;
QFrame *fontSeparator = nullptr;
QWidget *fontSection = nullptr;
QLabel *fontNoteLabel = nullptr;
QWidget *footer; QWidget *footer;
QComboBox *schemeComboBox = nullptr; QComboBox *schemeComboBox = nullptr;
QDialogButtonBox *buttonBox = nullptr; QDialogButtonBox *buttonBox = nullptr;
@ -68,10 +77,18 @@ private:
QMap<QString, PaletteConfig> workingConfig; QMap<QString, PaletteConfig> workingConfig;
QMap<QString, PaletteConfig> savedConfig; QMap<QString, PaletteConfig> savedConfig;
// Fonts are shared per theme (fonts.toml, not split by light/dark), so a
// single saved config suffices; the live edits are read from fontGrid.
FontConfig savedFontConfig;
// Set only by user-driven fontsChanged after the initial load completes.
// Gates both previewFonts and the fonts.toml write so a palette-only Save
// never materializes fallback fonts to disk.
bool fontsDirty = false;
QTimer *autoApplyTimer = nullptr; QTimer *autoApplyTimer = nullptr;
protected: protected:
void changeEvent(QEvent *e) override; void changeEvent(QEvent *e) override;
}; };
#endif // COCKATRICE_PALETTE_EDITOR_DIALOG_H #endif // COCKATRICE_PALETTE_EDITOR_DIALOG_H

View file

@ -76,6 +76,18 @@ PaletteGridWidget::PaletteGridWidget(QWidget *parent) : QWidget(parent)
layout = QtUtils::makeLayout<QVBoxLayout>(this); layout = QtUtils::makeLayout<QVBoxLayout>(this);
layout->addWidget(scroll); layout->addWidget(scroll);
connect(themeManager, &ThemeManager::fontsChanged, this, &PaletteGridWidget::applyInterfaceDefaultFont);
}
void PaletteGridWidget::applyInterfaceDefaultFont()
{
// The editor's own chrome follows the InterfaceDefault theme role; bold stays caller-side.
for (auto *label : headerLabels) {
QFont headerFont = themeManager->appFont(AppFont::InterfaceDefault);
headerFont.setBold(true);
label->setFont(headerFont);
}
} }
void PaletteGridWidget::buildGrid(QWidget *host) void PaletteGridWidget::buildGrid(QWidget *host)
@ -101,7 +113,8 @@ void PaletteGridWidget::buildGrid(QWidget *host)
auto *label = new QLabel(groupHeaders[col], host); auto *label = new QLabel(groupHeaders[col], host);
label->setAlignment(Qt::AlignCenter); label->setAlignment(Qt::AlignCenter);
label->setToolTip(groupTips[col]); label->setToolTip(groupTips[col]);
label->setFont(QtUtils::boldFont(label->font())); // Family comes from the InterfaceDefault theme role; bold stays caller-side.
label->setFont(QtUtils::boldFont(themeManager->appFont(AppFont::InterfaceDefault)));
label->setAutoFillBackground(true); label->setAutoFillBackground(true);
label->setContentsMargins(4, 4, 4, 4); label->setContentsMargins(4, 4, 4, 4);
grid->addWidget(label, 0, col + 1); grid->addWidget(label, 0, col + 1);
@ -146,7 +159,7 @@ void PaletteGridWidget::buildGrid(QWidget *host)
auto *appHeader = new QLabel(tr("App colors"), host); auto *appHeader = new QLabel(tr("App colors"), host);
appHeader->setToolTip(tr("Application-specific colors layered on top of the Qt palette")); appHeader->setToolTip(tr("Application-specific colors layered on top of the Qt palette"));
appHeader->setFont(QtUtils::boldFont(appHeader->font())); appHeader->setFont(QtUtils::boldFont(themeManager->appFont(AppFont::InterfaceDefault)));
appHeader->setAutoFillBackground(true); appHeader->setAutoFillBackground(true);
appHeader->setContentsMargins(4, 4, 4, 4); appHeader->setContentsMargins(4, 4, 4, 4);
grid->addWidget(appHeader, appHeaderRow, 0, 1, 4); grid->addWidget(appHeader, appHeaderRow, 0, 1, 4);

View file

@ -34,6 +34,10 @@ private:
void changeEvent(QEvent *e); void changeEvent(QEvent *e);
void refreshChromePalettes(); void refreshChromePalettes();
private slots:
void applyInterfaceDefaultFont();
private:
QMap<QPalette::ColorGroup, QMap<QPalette::ColorRole, ColorButton *>> colorButtons; QMap<QPalette::ColorGroup, QMap<QPalette::ColorRole, ColorButton *>> colorButtons;
QMap<AppColor::Role, ColorButton *> appColorButtons; QMap<AppColor::Role, ColorButton *> appColorButtons;
QScrollArea *scroll; QScrollArea *scroll;

View file

@ -1,5 +1,6 @@
#include "quick_setup_panel.h" #include "quick_setup_panel.h"
#include "../theme_manager.h"
#include "color_button.h" #include "color_button.h"
#include <QApplication> #include <QApplication>
@ -28,10 +29,15 @@ QuickSetupPanel::QuickSetupPanel(QWidget *parent) : QWidget(parent)
labelLow = new QLabel(this); labelLow = new QLabel(this);
labelHigh = new QLabel(this); labelHigh = new QLabel(this);
QFont small = labelLow->font(); // Family comes from the InterfaceDefault theme role; the *0.82 size stays caller-side.
small.setPointSizeF(small.pointSizeF() * 0.82); auto applySmallFont = [this]() {
labelLow->setFont(small); QFont small = themeManager->appFont(AppFont::InterfaceDefault);
labelHigh->setFont(small); small.setPointSizeF(small.pointSizeF() * 0.82);
labelLow->setFont(small);
labelHigh->setFont(small);
};
applySmallFont();
connect(themeManager, &ThemeManager::fontsChanged, this, applySmallFont);
QPalette dimmed = labelLow->palette(); QPalette dimmed = labelLow->palette();
dimmed.setColor(QPalette::WindowText, qApp->palette().color(QPalette::Mid)); dimmed.setColor(QPalette::WindowText, qApp->palette().color(QPalette::Mid));
labelLow->setPalette(dimmed); labelLow->setPalette(dimmed);

View file

@ -304,4 +304,150 @@ QPalette PaletteConfig::apply(QPalette base) const
} }
return base; return base;
} }
bool FontConfig::hasFonts() const
{
return !fonts.isEmpty();
}
QString FontConfig::toToml() const
{
QMetaEnum roleEnum = QMetaEnum::fromType<AppFont::Role>();
QString entries;
for (auto it = fonts.cbegin(); it != fonts.cend(); ++it) {
const char *roleName = roleEnum.valueToKey(it.key());
if (!roleName) {
continue;
}
QString family = it.value().family().trimmed();
int pointSize = it.value().pointSize();
if (family.isEmpty() || pointSize <= 0) {
continue;
}
entries += QString("%1 = \"%2, %3\"\n").arg(QString(roleName), -20).arg(family).arg(pointSize);
}
if (entries.isEmpty()) {
return {};
}
return "[Fonts]\n" + entries + "\n";
}
QString FontConfig::fileName()
{
return "fonts.toml";
}
FontConfig FontConfig::fromFile(const QString &filePath)
{
FontConfig cfg;
QFile f(filePath);
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
return cfg;
}
QMetaEnum roleEnum = QMetaEnum::fromType<AppFont::Role>();
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();
// Strip inline comments if preceded by whitespace
for (int i = 1; i < value.size(); ++i) {
if ((value[i] == '#' || value[i] == ';') && value[i - 1].isSpace()) {
value = value.left(i).trimmed();
break;
}
}
// Strip surrounding quotes, if any
if (value.size() >= 2 &&
((value.startsWith('"') && value.endsWith('"')) || (value.startsWith('\'') && value.endsWith('\'')))) {
value = value.mid(1, value.size() - 2).trimmed();
}
int comma = value.lastIndexOf(',');
if (comma < 0) {
continue;
}
QString family = value.left(comma).trimmed();
// Tolerate a partially-quoted family, e.g. "Inter", 10
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;
}
if (key.startsWith("AppFont::")) {
key = key.mid(QStringLiteral("AppFont::").size());
}
int roleInt = roleEnum.keyToValue(key.toUtf8().constData());
if (roleInt < 0) {
continue;
}
// Canonical family + point size only: a fresh QFont carries default
// (non-bold, non-italic) weight/style, so nothing caller-side leaks in.
// No validation against installed fonts; Qt substitution applies.
QFont font(family);
font.setPointSize(pointSize);
cfg.fonts[static_cast<AppFont::Role>(roleInt)] = font;
}
return cfg;
}
FontConfig FontConfig::fromThemeDir(const QString &themeDirPath)
{
if (themeDirPath.isEmpty()) {
return {};
}
return fromFile(QDir(themeDirPath).absoluteFilePath(fileName()));
}

View file

@ -2,6 +2,7 @@
#define COCKATRICE_THEME_CONFIG_H #define COCKATRICE_THEME_CONFIG_H
#include <QColor> #include <QColor>
#include <QFont>
#include <QMap> #include <QMap>
#include <QPalette> #include <QPalette>
#include <QSharedPointer> #include <QSharedPointer>
@ -23,6 +24,23 @@ enum Role
Q_ENUM_NS(Role) Q_ENUM_NS(Role)
} // namespace AppColor } // namespace AppColor
// Application-specific font roles, one configurable family + point size per
// purpose. Stored in a shared fonts.toml under a [Fonts] section (one file per
// theme, not split by light/dark). Only family and point size are stored;
// weight/italic are stripped on load and stay caller-side concerns.
namespace AppFont
{
Q_NAMESPACE
enum Role
{
InterfaceDefault,
ChatLog,
CardText,
DeckList,
};
Q_ENUM_NS(Role)
} // namespace AppFont
struct ThemeConfig struct ThemeConfig
{ {
QString colorScheme; QString colorScheme;
@ -60,4 +78,33 @@ struct PaletteConfig
QPalette apply(QPalette base) const; QPalette apply(QPalette base) const;
}; };
#endif // COCKATRICE_THEME_CONFIG_H /** @brief Per-purpose font configuration, stored as family + point size only.
*
* Serialized to a shared fonts.toml file with a [Fonts] section, e.g.
* @code
* [Fonts]
* InterfaceDefault = "Inter, 10"
* @endcode
*/
struct FontConfig
{
QMap<AppFont::Role, QFont> fonts;
bool operator==(const FontConfig &rhs) const
{
return fonts == rhs.fonts;
}
bool operator!=(const FontConfig &rhs) const
{
return !(*this == rhs);
}
bool hasFonts() const;
QString toToml() const;
static QString fileName();
static FontConfig fromFile(const QString &filePath);
static FontConfig fromThemeDir(const QString &themeDirPath);
};
#endif // COCKATRICE_THEME_CONFIG_H

View file

@ -12,6 +12,7 @@
#include <QFile> #include <QFile>
#include <QFileInfo> #include <QFileInfo>
#include <QFlags> #include <QFlags>
#include <QFontDatabase>
#include <QGuiApplication> #include <QGuiApplication>
#include <QIODevice> #include <QIODevice>
#include <QLatin1Char> #include <QLatin1Char>
@ -127,8 +128,10 @@ static QString usableDefaultStyle(const QString &style)
ThemeManager::ThemeManager(QObject *parent) : QObject(parent) ThemeManager::ThemeManager(QObject *parent) : QObject(parent)
{ {
defaultStyleName = usableDefaultStyle(qApp->style()->objectName()); defaultStyleName = usableDefaultStyle(qApp->style()->objectName());
// Capture the untouched application palette before any theme is applied. // Capture the untouched application palette and font before any theme is
// applied.
defaultPalette = qApp->palette(); defaultPalette = qApp->palette();
defaultFont = qApp->font();
ensureThemeDirectoryExists(); ensureThemeDirectoryExists();
#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0))
connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this, [this] { connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this, [this] {
@ -440,6 +443,46 @@ PaletteConfig ThemeManager::loadDefaultPaletteConfig(const QString &themeDirPath
return cfg; return cfg;
} }
FontConfig ThemeManager::loadFontConfig(const QString &themeDirPath)
{
if (themeDirPath.isEmpty()) {
return {};
}
return FontConfig::fromThemeDir(themeDirPath);
}
FontConfig ThemeManager::loadDefaultFontConfig(const QString &themeDirPath, const QString &themeName)
{
FontConfig cfg = FontConfig::fromThemeDir(themeDirPath);
if (!cfg.hasFonts()) {
// The shipped fonts.toml may live in the system theme directory rather
// than the resolved (user) theme directory, so built-in themes still
// get their curated fonts.
cfg = FontConfig::fromThemeDir(QDir(systemThemesBasePath()).absoluteFilePath(themeName));
}
return cfg;
}
bool ThemeManager::saveFontConfig(const QString &themeDirPath, const FontConfig &cfg)
{
if (themeDirPath.isEmpty()) {
return false;
}
QDir dir(themeDirPath);
if (!dir.exists()) {
dir.mkpath(".");
}
QFile f(dir.absoluteFilePath(FontConfig::fileName()));
if (!f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
return false;
}
QTextStream(&f) << cfg.toToml();
return true;
}
bool ThemeManager::commitPalette(const QString &themeDirPath, const QString &colorScheme, const PaletteConfig &cfg) bool ThemeManager::commitPalette(const QString &themeDirPath, const QString &colorScheme, const PaletteConfig &cfg)
{ {
if (!savePaletteConfig(themeDirPath, colorScheme, cfg)) { if (!savePaletteConfig(themeDirPath, colorScheme, cfg)) {
@ -488,6 +531,24 @@ void ThemeManager::previewPalette(const PaletteConfig &cfg, const QString &schem
applyStyleAndPalette(themeName, themeCfg, cfg, scheme); applyStyleAndPalette(themeName, themeCfg, cfg, scheme);
} }
void ThemeManager::previewFonts(const FontConfig &cfg)
{
currentFonts = cfg;
// QApplication::setFont already sends a font change to every widget, so a
// repolish pass is not needed for fonts (unlike the palette path).
const auto it = cfg.fonts.constFind(AppFont::InterfaceDefault);
if (it != cfg.fonts.constEnd()) {
qApp->setFont(it.value());
} else {
// A config without InterfaceDefault means "no override": restore the
// startup font instead of leaving the last preview applied.
qApp->setFont(defaultFont);
}
emit fontsChanged();
}
void ThemeManager::applyStyleAndPalette(const QString &themeName, void ThemeManager::applyStyleAndPalette(const QString &themeName,
const ThemeConfig &themeCfg, const ThemeConfig &themeCfg,
const PaletteConfig &palCfg, const PaletteConfig &palCfg,
@ -591,6 +652,56 @@ QColor ThemeManager::appColor(AppColor::Role role) const
return accent; return accent;
} }
QFont ThemeManager::appFont(AppFont::Role role) const
{
const auto it = currentFonts.fonts.constFind(role);
if (it != currentFonts.fonts.constEnd()) {
return it.value();
}
// Role fallbacks preserve the pre-theme defaults when no fonts.toml is
// present: an unset role resolves to the application font. Surfaces that
// hardcoded a different family ask for it explicitly: counter painters
// through cardTextFont(), the deck view headers and printed deck list
// through deckListFont(), the report dialogs through
// report_utils::reportLogFont(). Only the family basis is fixed there;
// sizes stay caller-side.
return qApp->font();
}
bool ThemeManager::hasAppFont(AppFont::Role role) const
{
return currentFonts.fonts.contains(role);
}
QFont ThemeManager::cardTextFont() const
{
if (hasAppFont(AppFont::CardText)) {
return appFont(AppFont::CardText);
}
QFont serifFont(QStringLiteral("Serif"));
serifFont.setStyleHint(QFont::Serif);
return serifFont;
}
QFont ThemeManager::deckListFont() const
{
if (hasAppFont(AppFont::DeckList)) {
return appFont(AppFont::DeckList);
}
QFont serifFont(QStringLiteral("Serif"));
serifFont.setStyleHint(QFont::Serif);
return serifFont;
}
QFont ThemeManager::defaultAppFont(AppFont::Role /*role*/) const
{
// No per-role scaling and no preview: an unset role resolves to the font
// captured at startup, which is what the theme falls back to once the
// editor closes.
return defaultFont;
}
void ThemeManager::themeChangedSlot() void ThemeManager::themeChangedSlot()
{ {
QString themeName = SettingsCache::instance().getThemeName(); QString themeName = SettingsCache::instance().getThemeName();
@ -642,6 +753,19 @@ void ThemeManager::themeChangedSlot()
applyStyleAndPalette(themeName, themeCfg, palette, activeScheme); applyStyleAndPalette(themeName, themeCfg, palette, activeScheme);
// ── Load fonts: shared per theme, no light/dark split ───────────────────
FontConfig fonts = loadDefaultFontConfig(dirPath, themeName);
currentFonts = fonts;
const auto fontIt = currentFonts.fonts.constFind(AppFont::InterfaceDefault);
if (fontIt != currentFonts.fonts.constEnd()) {
qApp->setFont(fontIt.value());
} else {
// No theme font: restore the pristine application font captured at
// startup, so a reverted preview cannot leak into the live theme.
qApp->setFont(defaultFont);
}
emit fontsChanged();
QStringList resources; QStringList resources;
if (!dirPath.isEmpty()) { if (!dirPath.isEmpty()) {
resources << dir.absolutePath(); resources << dir.absolutePath();

View file

@ -11,6 +11,7 @@
#include <QBrush> #include <QBrush>
#include <QColor> #include <QColor>
#include <QFont>
#include <QLoggingCategory> #include <QLoggingCategory>
#include <QMap> #include <QMap>
#include <QObject> #include <QObject>
@ -47,10 +48,15 @@ private:
// palette is applied. Used as the base when a theme supplies no palette, so // palette is applied. Used as the base when a theme supplies no palette, so
// switching away from a custom palette restores the original colours. // switching away from a custom palette restores the original colours.
QPalette defaultPalette; QPalette defaultPalette;
// Pristine application font captured at startup, before any theme font is
// applied. Restored when a theme (or a preview) supplies no InterfaceDefault
// font, so switching away from a themed font cannot leave it behind.
QFont defaultFont;
QString currentThemePath; QString currentThemePath;
std::array<QBrush, Role::MaxRole + 1> brushes; std::array<QBrush, Role::MaxRole + 1> brushes;
QStringMap availableThemes; QStringMap availableThemes;
QMap<AppColor::Role, QColor> currentAppColors; QMap<AppColor::Role, QColor> currentAppColors;
FontConfig currentFonts;
/* /*
Internal cache for multiple backgrounds Internal cache for multiple backgrounds
*/ */
@ -105,6 +111,9 @@ public:
// Load/save per-scheme palette colors // Load/save per-scheme palette colors
static PaletteConfig loadPaletteConfig(const QString &themeDirPath, const QString &colorScheme); static PaletteConfig loadPaletteConfig(const QString &themeDirPath, const QString &colorScheme);
static bool savePaletteConfig(const QString &themeDirPath, const QString &colorScheme, const PaletteConfig &cfg); static bool savePaletteConfig(const QString &themeDirPath, const QString &colorScheme, const PaletteConfig &cfg);
// Load/save shared per-theme fonts (fonts.toml, not split by scheme)
static FontConfig loadFontConfig(const QString &themeDirPath);
static bool saveFontConfig(const QString &themeDirPath, const FontConfig &cfg);
// Resolve prefix to a scheme-qualified "theme:" path. Existence is probed // Resolve prefix to a scheme-qualified "theme:" path. Existence is probed
// internally across the formats themes may ship (.png/.jpg/.svg), so // internally across the formats themes may ship (.png/.jpg/.svg), so
// callers load the returned path directly. Prefers "<prefix>-<dark|light>" // callers load the returned path directly. Prefers "<prefix>-<dark|light>"
@ -123,6 +132,9 @@ public:
// theme directory when it is absent from the resolved (user) directory. // theme directory when it is absent from the resolved (user) directory.
static PaletteConfig static PaletteConfig
loadDefaultPaletteConfig(const QString &themeDirPath, const QString &themeName, const QString &colorScheme); loadDefaultPaletteConfig(const QString &themeDirPath, const QString &themeName, const QString &colorScheme);
// Load the theme's shipped default fonts, falling back to the system theme
// directory when fonts.toml is absent from the resolved (user) directory.
static FontConfig loadDefaultFontConfig(const QString &themeDirPath, const QString &themeName);
/** @brief Writes cfg to disk as the theme's palette-<scheme>.toml and updates the /** @brief Writes cfg to disk as the theme's palette-<scheme>.toml and updates the
* theme's stored colour scheme to match. Shared by PaletteEditorDialog::onSave * theme's stored colour scheme to match. Shared by PaletteEditorDialog::onSave
* and FirstRunWizard's theme step so the two "generate + keep" paths can't drift. */ * and FirstRunWizard's theme step so the two "generate + keep" paths can't drift. */
@ -132,10 +144,31 @@ public:
void reloadCurrentTheme(); void reloadCurrentTheme();
void previewPalette(const PaletteConfig &cfg, const QString &scheme); void previewPalette(const PaletteConfig &cfg, const QString &scheme);
void previewFonts(const FontConfig &cfg);
// Resolves an application color role: the theme's stored [AppColors] value // Resolves an application color role: the theme's stored [AppColors] value
// when present, otherwise a palette-accent-derived fallback. // when present, otherwise a palette-accent-derived fallback.
QColor appColor(AppColor::Role role) const; QColor appColor(AppColor::Role role) const;
/** @brief Resolves an application font role: the theme's stored [Fonts]
* value when present, otherwise the application font. Roles that
* had a historical hardcoded family ask for it through
* cardTextFont() and deckListFont() instead. */
QFont appFont(AppFont::Role role) const;
/** @brief Whether the active theme defines this role in fonts.toml. */
bool hasAppFont(AppFont::Role role) const;
/** @brief The CardText role with its historical Serif fallback. Used by the
* counter painters, which were Serif before the role existed. */
QFont cardTextFont() const;
/** @brief The DeckList role with its historical Serif fallback. Used by the
* two surfaces that hardcoded Serif before the role existed (the
* deck view zone headers and the printed deck list); every other
* DeckList reader falls back to the application font. */
QFont deckListFont() const;
/** @brief Resolves an application font role against the startup font only,
* ignoring any previewed config in currentFonts. Editors that show
* "what this role resolves to" must use this, because appFont()
* reflects a live preview while the editor is open. */
QFont defaultAppFont(AppFont::Role role) const;
QBrush &getBgBrush(Role zone); QBrush &getBgBrush(Role zone);
QBrush getExtraBgBrush(Role zone, int zoneId = 0); QBrush getExtraBgBrush(Role zone, int zoneId = 0);
@ -144,6 +177,7 @@ protected slots:
signals: signals:
void themeChanged(); void themeChanged();
void paletteChanged(); void paletteChanged();
void fontsChanged();
}; };
extern ThemeManager *themeManager; extern ThemeManager *themeManager;

View file

@ -1,5 +1,6 @@
#include "art_crop_attribution.h" #include "art_crop_attribution.h"
#include "../../theme_manager.h"
#include "libcockatrice/card/printing/printing_info.h" #include "libcockatrice/card/printing/printing_info.h"
#include <QColor> #include <QColor>
@ -35,7 +36,10 @@ QRectF paintArtAttribution(QPainter &painter,
painter.save(); painter.save();
// Family comes from the CardText theme role; the incoming painter size
// stays the basis for the scale factor exactly as before.
QFont font = painter.font(); QFont font = painter.font();
font.setFamily(themeManager->appFont(AppFont::CardText).family());
font.setPointSizeF(qMax(6.0, font.pointSizeF() * scale)); font.setPointSizeF(qMax(6.0, font.pointSizeF() * scale));
painter.setFont(font); painter.setFont(font);

View file

@ -1,5 +1,7 @@
#include "card_info_picture_with_text_overlay_widget.h" #include "card_info_picture_with_text_overlay_widget.h"
#include "../../theme_manager.h"
#include <QBrush> #include <QBrush>
#include <QChar> #include <QChar>
#include <QFont> #include <QFont>
@ -43,6 +45,7 @@ CardInfoPictureWithTextOverlayWidget::CardInfoPictureWithTextOverlayWidget(QWidg
fontSize(fontSize), textAlignment(alignment), highlighted(false) fontSize(fontSize), textAlignment(alignment), highlighted(false)
{ {
this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
connect(themeManager, &ThemeManager::fontsChanged, this, &CardInfoPictureWithTextOverlayWidget::applyCardTextFont);
} }
/** /**
@ -97,6 +100,13 @@ void CardInfoPictureWithTextOverlayWidget::setTextAlignment(const Qt::Alignment
update(); update();
} }
void CardInfoPictureWithTextOverlayWidget::applyCardTextFont()
{
// The overlay paint reads the CardText role font at paint time, so a
// theme font change only needs a repaint here.
update();
}
void CardInfoPictureWithTextOverlayWidget::setHighlighted(bool _highlighted) void CardInfoPictureWithTextOverlayWidget::setHighlighted(bool _highlighted)
{ {
if (highlighted == _highlighted) { if (highlighted == _highlighted) {
@ -168,8 +178,10 @@ void CardInfoPictureWithTextOverlayWidget::paintEvent(QPaintEvent *event)
return; return;
} }
// Calculate the optimal font size // Calculate the optimal font size. The family comes from the CardText
QFont font = painter.font(); // theme role; the fontSize member stays the starting size and the
// shrink-to-fit logic below is unchanged.
QFont font = themeManager->appFont(AppFont::CardText);
int optimalFontSize = fontSize; // Start with the user-defined font size int optimalFontSize = fontSize; // Start with the user-defined font size
const QFontMetrics baseMetrics(font); const QFontMetrics baseMetrics(font);
int textWidth = pixmapRect.width(); int textWidth = pixmapRect.width();
@ -284,7 +296,7 @@ QSize CardInfoPictureWithTextOverlayWidget::minimumSizeHint() const
const QSize pixmapSize = pixmap.isNull() ? QSize(0, 0) : pixmap.size(); const QSize pixmapSize = pixmap.isNull() ? QSize(0, 0) : pixmap.size();
// Get the font metrics for the overlay text // Get the font metrics for the overlay text
QFont font; QFont font = themeManager->appFont(AppFont::CardText);
font.setPointSize(fontSize); font.setPointSize(fontSize);
const QFontMetrics fontMetrics(font); const QFontMetrics fontMetrics(font);

View file

@ -47,6 +47,9 @@ protected:
void mousePressEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override;
[[nodiscard]] QSize minimumSizeHint() const override; [[nodiscard]] QSize minimumSizeHint() const override;
private slots:
void applyCardTextFont();
private: private:
void drawOutlinedText(QPainter &painter, void drawOutlinedText(QPainter &painter,
const QRect &textRect, const QRect &textRect,

View file

@ -1,5 +1,6 @@
#include "resizable_panel.h" #include "resizable_panel.h"
#include "../interface/theme_manager.h"
#include "abstract_analytics_panel_widget.h" #include "abstract_analytics_panel_widget.h"
#include "libcockatrice/utility/qt_utils.h" #include "libcockatrice/utility/qt_utils.h"
@ -12,6 +13,7 @@
#include <QDropEvent> #include <QDropEvent>
#include <QEvent> #include <QEvent>
#include <QFlags> #include <QFlags>
#include <QFont>
#include <QFrame> #include <QFrame>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QMimeData> #include <QMimeData>
@ -93,9 +95,17 @@ ResizablePanel::ResizablePanel(const QString &_typeId, AbstractAnalyticsPanelWid
dragButton->setStyleSheet(QString("QPushButton { " dragButton->setStyleSheet(QString("QPushButton { "
"background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 %1, stop:1 %2); " "background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 %1, stop:1 %2); "
"border: none; color: %3; font-size: 10px; }" "border: none; color: %3; }"
"QPushButton:hover { background: %4; }") "QPushButton:hover { background: %4; }")
.arg(mid.name(), dark.name(), placeholderText.name(), midLight.name())); .arg(mid.name(), dark.name(), placeholderText.name(), midLight.name()));
// Family comes from the InterfaceDefault theme role; the 10px size stays caller-side.
auto applyDragFont = [this]() {
QFont dragFont = themeManager->appFont(AppFont::InterfaceDefault);
dragFont.setPixelSize(10);
dragButton->setFont(dragFont);
};
applyDragFont();
connect(themeManager, &ThemeManager::fontsChanged, this, applyDragFont);
resizeHandle->setStyleSheet(QString("QWidget { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, " resizeHandle->setStyleSheet(QString("QWidget { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, "
"stop:0 %1, stop:1 %2); }") "stop:0 %1, stop:1 %2); }")

View file

@ -49,6 +49,7 @@
#include <QPushButton> #include <QPushButton>
#include <QSet> #include <QSet>
#include <QSharedPointer> #include <QSharedPointer>
#include <QSignalBlocker>
#include <QSize> #include <QSize>
#include <QSizePolicy> #include <QSizePolicy>
#include <QSplitter> #include <QSplitter>
@ -187,10 +188,17 @@ void DeckEditorDeckDockWidget::createDeckDock()
formatLabel = new QLabel(this); formatLabel = new QLabel(this);
formatComboBox = new QComboBox(this); formatComboBox = new QComboBox(this);
formatComboBox->setObjectName("deckFormatComboBox");
formatComboBox->addItem(tr("Loading Database...")); formatComboBox->addItem(tr("Loading Database..."));
formatComboBox->setEnabled(false); // Disable until loaded formatComboBox->setEnabled(false); // Disable until loaded
commanderBracketWidget = new CommanderBracketWidget(this); commanderBracketWidget = new CommanderBracketWidget(this);
connect(formatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) {
formatComboBox->setPlaceholderText(QString());
formatComboBox->setToolTip(QString());
deckStateManager->setFormat(index >= 0 ? formatComboBox->itemData(index).toString() : QString());
commanderBracketWidget->setDeck(deckStateManager->getDeckListShared());
});
commentsLabel = new QLabel(); commentsLabel = new QLabel();
commentsLabel->setObjectName("commentsLabel"); commentsLabel->setObjectName("commentsLabel");
@ -355,6 +363,9 @@ void DeckEditorDeckDockWidget::initializeFormats()
{ {
QStringList allFormats = CardDatabaseManager::query()->getAllFormatsWithCount().keys(); QStringList allFormats = CardDatabaseManager::query()->getAllFormatsWithCount().keys();
// Rebuilding the list is a view update, not a user edit. In particular,
// clear() emits currentIndexChanged(-1), which must not clear the deck.
const QSignalBlocker blocker(formatComboBox);
formatComboBox->clear(); // Remove "Loading Database..." formatComboBox->clear(); // Remove "Loading Database..."
formatComboBox->setEnabled(true); formatComboBox->setEnabled(true);
@ -364,28 +375,25 @@ void DeckEditorDeckDockWidget::initializeFormats()
formatComboBox->addItem(formatName, formatName); // store the raw key in itemData formatComboBox->addItem(formatName, formatName); // store the raw key in itemData
} }
QString format = deckStateManager->getMetadata().gameFormat; syncFormatSelection();
if (!format.isEmpty()) {
formatComboBox->setCurrentIndex(formatComboBox->findData(format));
} else {
// Ensure no selection is visible initially
formatComboBox->setCurrentIndex(-1);
}
connect(formatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) {
QString formatKey;
if (index >= 0) {
formatKey = formatComboBox->itemData(index).toString();
deckStateManager->setFormat(formatKey);
} else {
deckStateManager->setFormat(""); // clear format if deselected
}
commanderBracketWidget->setDeck(deckStateManager->getDeckListShared());
});
commanderBracketWidget->setDeck(deckStateManager->getDeckListShared()); commanderBracketWidget->setDeck(deckStateManager->getDeckListShared());
} }
void DeckEditorDeckDockWidget::syncFormatSelection()
{
if (!formatComboBox->isEnabled()) {
return;
}
const QSignalBlocker blocker(formatComboBox);
const QString format = deckStateManager->getMetadata().gameFormat;
const int index = format.isEmpty() ? -1 : formatComboBox->findData(format);
const QString unavailable = index < 0 && !format.isEmpty() ? tr("Unavailable: %1").arg(format) : QString();
formatComboBox->setPlaceholderText(unavailable);
formatComboBox->setToolTip(unavailable);
formatComboBox->setCurrentIndex(index);
}
ExactCard DeckEditorDeckDockWidget::getCurrentCard() ExactCard DeckEditorDeckDockWidget::getCurrentCard()
{ {
QModelIndex current = deckView->selectionModel()->currentIndex(); QModelIndex current = deckView->selectionModel()->currentIndex();
@ -596,9 +604,7 @@ void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel()
updatePlaymatLabel(); updatePlaymatLabel();
updateHash(); updateHash();
formatComboBox->blockSignals(true); syncFormatSelection();
formatComboBox->setCurrentIndex(formatComboBox->findData(deckStateManager->getMetadata().gameFormat));
formatComboBox->blockSignals(false);
commanderBracketWidget->setDeck(deckStateManager->getDeckListShared()); commanderBracketWidget->setDeck(deckStateManager->getDeckListShared());
@ -762,18 +768,6 @@ void DeckEditorDeckDockWidget::actSwapSelection()
update(); update();
} }
void DeckEditorDeckDockWidget::actDecrementCard(const ExactCard &card, QString zoneName)
{
if (!card) {
return;
}
if (card.getInfo().getIsToken()) {
zoneName = DECK_ZONE_TOKENS;
}
deckStateManager->decrementCard(card, zoneName);
}
void DeckEditorDeckDockWidget::actDecrementSelection() void DeckEditorDeckDockWidget::actDecrementSelection()
{ {
auto selectedRows = getSelectedCardNodeSourceIndices(); auto selectedRows = getSelectedCardNodeSourceIndices();
@ -1054,6 +1048,7 @@ void DeckEditorDeckDockWidget::retranslateUi()
commentsLabel->setText(tr("&Comments:")); commentsLabel->setText(tr("&Comments:"));
activeGroupCriteriaLabel->setText(tr("Group by:")); activeGroupCriteriaLabel->setText(tr("Group by:"));
formatLabel->setText(tr("Format:")); formatLabel->setText(tr("Format:"));
syncFormatSelection();
commanderBracketWidget->retranslateUi(); commanderBracketWidget->retranslateUi();
hashLabel1->setText(tr("Hash:")); hashLabel1->setText(tr("Hash:"));

View file

@ -70,7 +70,6 @@ public slots:
void syncDisplayWidgetsToModel(); void syncDisplayWidgetsToModel();
void actAddCard(const ExactCard &card, const QString &zoneName); void actAddCard(const ExactCard &card, const QString &zoneName);
void actIncrementSelection(); void actIncrementSelection();
void actDecrementCard(const ExactCard &card, QString zoneName);
void actDecrementSelection(); void actDecrementSelection();
void actSwapCard(const ExactCard &card, const QString &zoneName); void actSwapCard(const ExactCard &card, const QString &zoneName);
void actSwapSelection(); void actSwapSelection();
@ -111,6 +110,7 @@ private:
DeckListModel *getModel() const; DeckListModel *getModel() const;
[[nodiscard]] QModelIndexList getSelectedCardNodeSourceIndices() const; [[nodiscard]] QModelIndexList getSelectedCardNodeSourceIndices() const;
void offsetCountAtIndex(const QModelIndex &idx, bool isIncrement); void offsetCountAtIndex(const QModelIndex &idx, bool isIncrement);
void syncFormatSelection();
void addMoveToZoneMenu(QMenu *menu, const QModelIndex &sourceCardIndex, const QString &currentBoardName); void addMoveToZoneMenu(QMenu *menu, const QModelIndex &sourceCardIndex, const QString &currentBoardName);
void addChangeBoardMenu(QMenu *menu, const QString &zoneName); void addChangeBoardMenu(QMenu *menu, const QString &zoneName);

View file

@ -1,5 +1,7 @@
#include "deck_list_style_proxy.h" #include "deck_list_style_proxy.h"
#include "../../theme_manager.h"
#include <QBrush> #include <QBrush>
#include <QColor> #include <QColor>
#include <QFont> #include <QFont>
@ -8,6 +10,16 @@
#include <libcockatrice/models/deck_list/deck_list_model.h> #include <libcockatrice/models/deck_list/deck_list_model.h>
#include <qnamespace.h> #include <qnamespace.h>
DeckListStyleProxy::DeckListStyleProxy(QObject *parent) : QIdentityProxyModel(parent)
{
// FontRole data reads the DeckList role live; rebroadcast so attached
// views re-query instead of waiting for an incidental repaint.
connect(themeManager, &ThemeManager::fontsChanged, this, [this] {
emit layoutAboutToBeChanged();
emit layoutChanged();
});
}
QVariant DeckListStyleProxy::data(const QModelIndex &index, int role) const QVariant DeckListStyleProxy::data(const QModelIndex &index, int role) const
{ {
QModelIndex src = mapToSource(index); QModelIndex src = mapToSource(index);
@ -20,7 +32,8 @@ QVariant DeckListStyleProxy::data(const QModelIndex &index, int role) const
bool isCard = src.data(DeckRoles::IsCardRole).toBool(); bool isCard = src.data(DeckRoles::IsCardRole).toBool();
if (role == Qt::FontRole && !isCard) { if (role == Qt::FontRole && !isCard) {
QFont f; // Family comes from the DeckList theme role; bold stays caller-side.
QFont f = themeManager->appFont(AppFont::DeckList);
f.setBold(true); f.setBold(true);
return f; return f;
} }

View file

@ -8,7 +8,7 @@ class DeckListStyleProxy : public QIdentityProxyModel
{ {
Q_OBJECT Q_OBJECT
public: public:
using QIdentityProxyModel::QIdentityProxyModel; explicit DeckListStyleProxy(QObject *parent = nullptr);
[[nodiscard]] QVariant data(const QModelIndex &index, int role) const override; [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override;
}; };

View file

@ -211,15 +211,15 @@ QModelIndex DeckStateManager::addCard(const ExactCard &card, const QString &zone
return {}; return {};
} }
QString zone = card.getInfo().getIsToken() ? DECK_ZONE_TOKENS : zoneName; QString targetZone = card.getInfo().getIsToken() ? DECK_ZONE_TOKENS : zoneName;
CardSetPtr set = card.getPrinting().getSet(); CardSetPtr set = card.getPrinting().getSet();
QString setName = set ? set->getCorrectedShortName() : ""; QString setName = set ? set->getCorrectedShortName() : "";
QString reason = QString reason =
tr("Added (%1): %2 (%3) %4").arg(zone, card.getName(), setName, card.getPrinting().getProperty("num")); tr("Added (%1): %2 (%3) %4").arg(targetZone, card.getName(), setName, card.getPrinting().getProperty("num"));
QModelIndex idx = modifyDeck(reason, [&card, &zone](auto model) { return model->addCard(card, zone); }); QModelIndex idx = modifyDeck(reason, [&card, &targetZone](auto model) { return model->addCard(card, targetZone); });
if (idx.isValid()) { if (idx.isValid()) {
emit focusIndexChanged(idx, true); emit focusIndexChanged(idx, true);
@ -234,10 +234,22 @@ QModelIndex DeckStateManager::decrementCard(const ExactCard &card, const QString
return {}; return {};
} }
QString targetZone = zoneName;
QString providerId = card.getPrinting().getUuid(); QString providerId = card.getPrinting().getUuid();
QString collectorNumber = card.getPrinting().getProperty("num"); QString collectorNumber = card.getPrinting().getProperty("num");
QModelIndex idx = deckListModel->findCard(card.getName(), zoneName, providerId, collectorNumber); QModelIndex idx = deckListModel->findCard(card.getName(), targetZone, providerId, collectorNumber);
// Tokens are usually added to the token zone, but it is possible for imported/hand-edited
// decks to contain them in the main or sideboards. This is why we only try to remove the
// token from the token zone if it does not exist in the given zone, allowing us to still
// remove it from there.
if (!idx.isValid() && card.getInfo().getIsToken() && targetZone != DECK_ZONE_TOKENS) {
targetZone = DECK_ZONE_TOKENS;
idx = deckListModel->findCard(card.getName(), targetZone, providerId, collectorNumber);
}
if (!idx.isValid()) { if (!idx.isValid()) {
return {}; return {};
} }
@ -249,7 +261,7 @@ QModelIndex DeckStateManager::decrementCard(const ExactCard &card, const QString
} }
// old index is no longer safe since rows could have been removed // old index is no longer safe since rows could have been removed
QModelIndex newIdx = deckListModel->findCard(card.getName(), zoneName, providerId, collectorNumber); QModelIndex newIdx = deckListModel->findCard(card.getName(), targetZone, providerId, collectorNumber);
if (newIdx.isValid()) { if (newIdx.isValid()) {
emit focusIndexChanged(newIdx, true); emit focusIndexChanged(newIdx, true);
} }

View file

@ -188,17 +188,18 @@ public:
///@} ///@}
/** /**
* @brief Adds the given card to the given zone. * @brief Adds the given card to the given zone. Tokens always go to DECK_ZONE_TOKENS, ignoring the given zone.
* Saves the operation to history if successful. * Saves the operation to history if successful.
* *
* @param card The card to add * @param card The card to add
* @param zoneName The zone to add the card to * @param zoneName The zone to add the card to if the card is not a token
* @return The index of the added card * @return The index of the added card
*/ */
QModelIndex addCard(const ExactCard &card, const QString &zoneName); QModelIndex addCard(const ExactCard &card, const QString &zoneName);
/** /**
* @brief Removes 1 copy of the given card from the given zone. * @brief Removes 1 copy of the given card from the given zone.
* If the card is a token and is not in the given zone, then it is removed from DECK_ZONE_TOKENS instead.
* Saves the operation to history if successful. * Saves the operation to history if successful.
* *
* @param card The card to remove * @param card The card to remove

View file

@ -22,6 +22,7 @@
#include <QNetworkReply> #include <QNetworkReply>
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QString> #include <QString>
#include <QStringList>
#include <QTextStream> #include <QTextStream>
#include <QUrl> #include <QUrl>
#include <QVBoxLayout> #include <QVBoxLayout>
@ -33,6 +34,7 @@
class QWidget; class QWidget;
static const QStringList SUPPORTED_PROVIDERS = {"Archidekt", "Deckstats", "ManaBox", "Moxfield", "TappedOut"};
DlgLoadDeckFromWebsite::DlgLoadDeckFromWebsite(QWidget *parent) : QDialog(parent) DlgLoadDeckFromWebsite::DlgLoadDeckFromWebsite(QWidget *parent) : QDialog(parent)
{ {
nam = NetworkManagerFactory::create(this); nam = NetworkManagerFactory::create(this);
@ -41,6 +43,7 @@ DlgLoadDeckFromWebsite::DlgLoadDeckFromWebsite(QWidget *parent) : QDialog(parent
setLayout(layout); setLayout(layout);
instructionLabel = new QLabel(this); instructionLabel = new QLabel(this);
instructionLabel->setWordWrap(true);
layout->addWidget(instructionLabel); layout->addWidget(instructionLabel);
urlEdit = new QLineEdit(this); urlEdit = new QLineEdit(this);
@ -64,8 +67,8 @@ DlgLoadDeckFromWebsite::DlgLoadDeckFromWebsite(QWidget *parent) : QDialog(parent
void DlgLoadDeckFromWebsite::retranslateUi() void DlgLoadDeckFromWebsite::retranslateUi()
{ {
instructionLabel->setText(tr("Paste a link to a decklist site here to import it.\n(Archidekt, Deckstats, Moxfield, " instructionLabel->setText(tr("Paste a link to a decklist site here to import it.") + "\n" +
"and TappedOut are supported.)")); tr("Supported services: %1").arg(SUPPORTED_PROVIDERS.join(", ")));
} }
bool DlgLoadDeckFromWebsite::testValidUrl() bool DlgLoadDeckFromWebsite::testValidUrl()
@ -84,8 +87,8 @@ void DlgLoadDeckFromWebsite::accept()
if (!jsonParser && info.provider != DeckProvider::Deckstats && info.provider != DeckProvider::TappedOut) { if (!jsonParser && info.provider != DeckProvider::Deckstats && info.provider != DeckProvider::TappedOut) {
qCWarning(DlgLoadDeckFromWebsiteLog) << "No parser found for provider"; qCWarning(DlgLoadDeckFromWebsiteLog) << "No parser found for provider";
QMessageBox::warning(this, tr("Load Deck from Website"), QMessageBox::warning(this, tr("Load Deck from Website"),
tr("No parser available for this deck provider.\n (Archidekt, Deckstats, Moxfield, " tr("No parser available for this deck provider.") + "\n" +
"and TappedOut are supported.)")); tr("Supported services: %1").arg(SUPPORTED_PROVIDERS.join(", ")));
QDialog::reject(); QDialog::reject();
return; return;
} }
@ -152,6 +155,7 @@ void DlgLoadDeckFromWebsite::accept()
"https://archidekt.com/decks/9999999\n" "https://archidekt.com/decks/9999999\n"
"https://deckstats.net/decks/99999/9999999-your-deck-name/en\n" "https://deckstats.net/decks/99999/9999999-your-deck-name/en\n"
"https://moxfield.com/decks/XYZxx-XYZ99Yyy-xyzXzzz\n" "https://moxfield.com/decks/XYZxx-XYZ99Yyy-xyzXzzz\n"
"https://manabox.app/decks/XYZxxa-YZ99Yyyx_yzXzzz\n"
"https://tappedout.net/mtg-decks/your-deck-name/")); "https://tappedout.net/mtg-decks/your-deck-name/"));
QDialog::reject(); QDialog::reject();
} }
@ -164,7 +168,9 @@ QSharedPointer<IJsonDeckParser> DlgLoadDeckFromWebsite::createParserForProvider(
return QSharedPointer<IJsonDeckParser>(new ArchidektJsonParser()); return QSharedPointer<IJsonDeckParser>(new ArchidektJsonParser());
case DeckProvider::Moxfield: case DeckProvider::Moxfield:
return QSharedPointer<IJsonDeckParser>(new MoxfieldJsonParser()); return QSharedPointer<IJsonDeckParser>(new MoxfieldJsonParser());
case DeckProvider::Manabox:
return QSharedPointer<IJsonDeckParser>(new ManaboxJsonParser());
default: default:
return QSharedPointer<IJsonDeckParser>(nullptr); return QSharedPointer<IJsonDeckParser>(nullptr);
} }
} }

View file

@ -1,11 +1,13 @@
#include "dlg_my_reports.h" #include "dlg_my_reports.h"
#include "../interface/theme_manager.h"
#include "../utility/report_utils.h" #include "../utility/report_utils.h"
#include "abstract_client.h" #include "abstract_client.h"
#include "libcockatrice/protocol/pb/response.pb.h" #include "libcockatrice/protocol/pb/response.pb.h"
#include "libcockatrice/protocol/pb/serverinfo_report.pb.h" #include "libcockatrice/protocol/pb/serverinfo_report.pb.h"
#include <QAbstractItemView> #include <QAbstractItemView>
#include <QFont>
#include <QGroupBox> #include <QGroupBox>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QHeaderView> #include <QHeaderView>
@ -24,7 +26,6 @@
#include <libcockatrice/protocol/pb/response_report_details.pb.h> #include <libcockatrice/protocol/pb/response_report_details.pb.h>
#include <libcockatrice/protocol/pb/response_report_my_list.pb.h> #include <libcockatrice/protocol/pb/response_report_my_list.pb.h>
#include <libcockatrice/protocol/pending_command.h> #include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/utility/qt_utils.h>
#include <qnamespace.h> #include <qnamespace.h>
#include <string> #include <string>
@ -69,7 +70,8 @@ DlgMyReports::DlgMyReports(AbstractClient *_client, QWidget *parent)
auto *chatGroup = new QGroupBox(tr("Chat Log Context")); auto *chatGroup = new QGroupBox(tr("Chat Log Context"));
chatLogEdit = new QTextEdit; chatLogEdit = new QTextEdit;
chatLogEdit->setReadOnly(true); chatLogEdit->setReadOnly(true);
chatLogEdit->setFont(QtUtils::monospaceFont()); applyChatLogFont();
connect(themeManager, &ThemeManager::fontsChanged, this, &DlgMyReports::applyChatLogFont);
auto *chatLayout = new QVBoxLayout(chatGroup); auto *chatLayout = new QVBoxLayout(chatGroup);
chatLayout->setContentsMargins(4, 4, 4, 4); chatLayout->setContentsMargins(4, 4, 4, 4);
chatLayout->addWidget(chatLogEdit); chatLayout->addWidget(chatLogEdit);
@ -122,6 +124,11 @@ DlgMyReports::DlgMyReports(AbstractClient *_client, QWidget *parent)
refreshList(); refreshList();
} }
void DlgMyReports::applyChatLogFont()
{
chatLogEdit->setFont(report_utils::reportLogFont());
}
void DlgMyReports::refreshList() void DlgMyReports::refreshList()
{ {
selectedReportIdBeforeRefresh = selectedReportId; selectedReportIdBeforeRefresh = selectedReportId;

View file

@ -29,6 +29,7 @@ private slots:
void reportDetailsResponse(const Response &response); void reportDetailsResponse(const Response &response);
void addComment(); void addComment();
void addCommentResponse(const Response &response); void addCommentResponse(const Response &response);
void applyChatLogFont();
private: private:
void loadReportDetails(const ServerInfo_Report &report); void loadReportDetails(const ServerInfo_Report &report);

View file

@ -1,10 +1,13 @@
#include "dlg_report_user.h" #include "dlg_report_user.h"
#include "../interface/theme_manager.h"
#include "../utility/report_utils.h"
#include "abstract_client.h" #include "abstract_client.h"
#include "libcockatrice/protocol/pb/response.pb.h" #include "libcockatrice/protocol/pb/response.pb.h"
#include <QComboBox> #include <QComboBox>
#include <QDialogButtonBox> #include <QDialogButtonBox>
#include <QFont>
#include <QGridLayout> #include <QGridLayout>
#include <QGroupBox> #include <QGroupBox>
#include <QLabel> #include <QLabel>
@ -91,7 +94,8 @@ DlgReportUser::DlgReportUser(AbstractClient *_client,
auto *chatLayout = new QVBoxLayout(chatGroup); auto *chatLayout = new QVBoxLayout(chatGroup);
chatLogEdit = new QTextEdit; chatLogEdit = new QTextEdit;
chatLogEdit->setReadOnly(true); chatLogEdit->setReadOnly(true);
chatLogEdit->setFont(QtUtils::monospaceFont()); applyChatLogFont();
connect(themeManager, &ThemeManager::fontsChanged, this, &DlgReportUser::applyChatLogFont);
if (!_autoChatLog.isEmpty()) { if (!_autoChatLog.isEmpty()) {
chatLogEdit->setPlainText(_autoChatLog); chatLogEdit->setPlainText(_autoChatLog);
} else { } else {
@ -120,6 +124,11 @@ DlgReportUser::DlgReportUser(AbstractClient *_client,
layout->addWidget(buttonBox); layout->addWidget(buttonBox);
} }
void DlgReportUser::applyChatLogFont()
{
chatLogEdit->setFont(report_utils::reportLogFont());
}
void DlgReportUser::actSubmit() void DlgReportUser::actSubmit()
{ {
const QString description = descriptionEdit->toPlainText().trimmed(); const QString description = descriptionEdit->toPlainText().trimmed();

View file

@ -27,6 +27,7 @@ public:
private slots: private slots:
void actSubmit(); void actSubmit();
void reportResponse(const Response &response); void reportResponse(const Response &response);
void applyChatLogFont();
private: private:
static QString categoryLabel(const QString &key); static QString categoryLabel(const QString &key);

View file

@ -2,6 +2,7 @@
#include "../deck_share/shared_deck_preview_widget.h" #include "../deck_share/shared_deck_preview_widget.h"
#include "../general/layout_containers/flow_widget.h" #include "../general/layout_containers/flow_widget.h"
#include "../interface/theme_manager.h"
#include <QAbstractButton> #include <QAbstractButton>
#include <QDateTime> #include <QDateTime>
@ -35,9 +36,14 @@ DlgSharedDecksPreview::DlgSharedDecksPreview(QWidget *parent,
// QLabels render AutoText and markup would otherwise be shown as rich text. // QLabels render AutoText and markup would otherwise be shown as rich text.
auto *titleLabel = auto *titleLabel =
new QLabel(tr("Share: %1").arg((shareName.isEmpty() ? tr("Untitled") : shareName).toHtmlEscaped()), this); new QLabel(tr("Share: %1").arg((shareName.isEmpty() ? tr("Untitled") : shareName).toHtmlEscaped()), this);
QFont titleFont = QtUtils::boldFont(titleLabel->font()); // Family comes from the InterfaceDefault theme role; the +2 size and bold stay caller-side.
titleFont.setPointSize(titleFont.pointSize() + 2); auto applyTitleFont = [titleLabel]() {
titleLabel->setFont(titleFont); QFont titleFont = QtUtils::boldFont(themeManager->appFont(AppFont::InterfaceDefault));
titleFont.setPointSize(titleFont.pointSize() + 2);
titleLabel->setFont(titleFont);
};
applyTitleFont();
connect(themeManager, &ThemeManager::fontsChanged, this, applyTitleFont);
mainLayout->addWidget(titleLabel); mainLayout->addWidget(titleLabel);
if (!serverText.isEmpty()) { if (!serverText.isEmpty()) {

View file

@ -7,6 +7,7 @@
#include "../../../client/settings/cache_settings.h" #include "../../../client/settings/cache_settings.h"
#include "../../pixel_map_generator.h" #include "../../pixel_map_generator.h"
#include "../interface/theme_manager.h"
#include "../settings_page/abstract_settings_page.h" #include "../settings_page/abstract_settings_page.h"
#include "../settings_page/appearance_settings_page.h" #include "../settings_page/appearance_settings_page.h"
#include "../settings_page/deck_editor_settings_page.h" #include "../settings_page/deck_editor_settings_page.h"
@ -199,6 +200,10 @@ void SettingsView::setupUi()
searchDelegate->setPageIcons(icons); searchDelegate->setPageIcons(icons);
searchResultsView->viewport()->update(); searchResultsView->viewport()->update();
}); });
// The search delegate reads the InterfaceDefault role at paint time, so a
// font change only needs a repaint, mirroring the theme change above.
connect(themeManager, &ThemeManager::fontsChanged, searchResultsView,
[this] { searchResultsView->viewport()->update(); });
// Build search index after pages are created // Build search index after pages are created
buildSearchIndex(); buildSearchIndex();

View file

@ -1,9 +1,11 @@
#include "banner_widget.h" #include "banner_widget.h"
#include "../../../pixel_map_generator.h" #include "../../../pixel_map_generator.h"
#include "../../../theme_manager.h"
#include <QBrush> #include <QBrush>
#include <QColor> #include <QColor>
#include <QFont>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QLinearGradient> #include <QLinearGradient>
#include <QPainter> #include <QPainter>
@ -28,7 +30,9 @@ BannerWidget::BannerWidget(QWidget *parent, const QString &text, Qt::Orientation
if (transparency > 50) { if (transparency > 50) {
textColor = " color: white;"; textColor = " color: white;";
} }
bannerLabel->setStyleSheet("font-size: 24px; font-weight: bold;" + textColor); bannerLabel->setStyleSheet(textColor);
applyInterfaceDefaultFont();
connect(themeManager, &ThemeManager::fontsChanged, this, &BannerWidget::applyInterfaceDefaultFont);
layout->addWidget(iconLabel); layout->addWidget(iconLabel);
layout->addWidget(bannerLabel); layout->addWidget(bannerLabel);
@ -56,6 +60,16 @@ void BannerWidget::setText(const QString &text) const
bannerLabel->setText(text); bannerLabel->setText(text);
} }
void BannerWidget::applyInterfaceDefaultFont()
{
// Family comes from the InterfaceDefault theme role; the 24px size and
// bold weight previously carried by the stylesheet stay caller-side.
QFont bannerFont = themeManager->appFont(AppFont::InterfaceDefault);
bannerFont.setPixelSize(24);
bannerFont.setWeight(QFont::Bold);
bannerLabel->setFont(bannerFont);
}
void BannerWidget::setClickable(bool _clickable) void BannerWidget::setClickable(bool _clickable)
{ {
clickable = _clickable; clickable = _clickable;

View file

@ -48,6 +48,7 @@ signals:
private slots: private slots:
void toggleBuddyVisibility() const; void toggleBuddyVisibility() const;
void updateDropdownIconState() const; void updateDropdownIconState() const;
void applyInterfaceDefaultFont();
}; };
#endif // BANNER_WIDGET_H #endif // BANNER_WIDGET_H

View file

@ -1,5 +1,7 @@
#include "bar_chart_widget.h" #include "bar_chart_widget.h"
#include "../../interface/theme_manager.h"
#include <QBrush> #include <QBrush>
#include <QFlags> #include <QFlags>
#include <QFont> #include <QFont>
@ -19,6 +21,10 @@
BarChartWidget::BarChartWidget(QWidget *parent) : QWidget(parent) BarChartWidget::BarChartWidget(QWidget *parent) : QWidget(parent)
{ {
setMouseTracking(true); setMouseTracking(true);
// Labels read the InterfaceDefault role at paint time, so a font change
// only needs a repaint.
connect(themeManager, &ThemeManager::fontsChanged, this, [this] { update(); });
} }
void BarChartWidget::setBars(const QVector<BarData> &newBars) void BarChartWidget::setBars(const QVector<BarData> &newBars)
@ -146,7 +152,8 @@ void BarChartWidget::paintEvent(QPaintEvent *)
// draw label below bar // draw label below bar
QRect labelRect(left + i * (barWidth + spacing), bottom, barWidth, LABEL_H); QRect labelRect(left + i * (barWidth + spacing), bottom, barWidth, LABEL_H);
QFont f = p.font(); // Family comes from the InterfaceDefault theme role; bold stays caller-side.
QFont f = themeManager->appFont(AppFont::InterfaceDefault);
f.setBold(true); f.setBold(true);
p.setFont(f); p.setFont(f);
p.setPen(Qt::black); p.setPen(Qt::black);

View file

@ -1,5 +1,7 @@
#include "segmented_bar_widget.h" #include "segmented_bar_widget.h"
#include "../../interface/theme_manager.h"
#include <QBrush> #include <QBrush>
#include <QFont> #include <QFont>
#include <QLinearGradient> #include <QLinearGradient>
@ -22,6 +24,10 @@ SegmentedBarWidget::SegmentedBarWidget(QString label, QVector<Segment> segments,
setMinimumWidth(36); setMinimumWidth(36);
setMaximumWidth(50); setMaximumWidth(50);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
// The label reads the InterfaceDefault role at paint time, so a font
// change only needs a repaint.
connect(themeManager, &ThemeManager::fontsChanged, this, [this] { update(); });
} }
QSize SegmentedBarWidget::sizeHint() const QSize SegmentedBarWidget::sizeHint() const
@ -95,7 +101,8 @@ void SegmentedBarWidget::paintEvent(QPaintEvent *)
// draw label // draw label
QRect labelRect(0, h - LABEL_H, w, LABEL_H); QRect labelRect(0, h - LABEL_H, w, LABEL_H);
QFont f = p.font(); // Family comes from the InterfaceDefault theme role; bold stays caller-side.
QFont f = themeManager->appFont(AppFont::InterfaceDefault);
f.setBold(true); f.setBold(true);
p.setFont(f); p.setFont(f);
p.setPen(Qt::black); p.setPen(Qt::black);

View file

@ -1,5 +1,7 @@
#include "dynamic_font_size_label.h" #include "dynamic_font_size_label.h"
#include "../../interface/theme_manager.h"
#include <QFont> #include <QFont>
#include <QFontMetricsF> #include <QFontMetricsF>
#include <QObject> #include <QObject>
@ -28,9 +30,13 @@ void DynamicFontSizeLabel::paintEvent(QPaintEvent *event)
// QElapsedTimer timer; // QElapsedTimer timer;
// timer.start(); // timer.start();
QFont newFont = font(); // Family follows the InterfaceDefault theme role; the fitted point size
// stays computed from the widget geometry as before.
QFont newFont = themeManager->appFont(AppFont::InterfaceDefault);
float fontSize = getWidgetMaximumFontSize(this, this->text()); float fontSize = getWidgetMaximumFontSize(this, this->text());
newFont.setPointSizeF(fontSize); newFont.setPointSizeF(fontSize);
// Preserve the caller's bold emphasis, which the role font does not carry.
newFont.setBold(font().bold());
setFont(newFont); setFont(newFont);
// qDebug() << "Font size set to" << fontSize; // qDebug() << "Font size set to" << fontSize;
@ -40,7 +46,10 @@ void DynamicFontSizeLabel::paintEvent(QPaintEvent *event)
float DynamicFontSizeLabel::getWidgetMaximumFontSize(QWidget *widget, const QString &text) float DynamicFontSizeLabel::getWidgetMaximumFontSize(QWidget *widget, const QString &text)
{ {
QFont font = widget->font(); // Measure with the InterfaceDefault family so the fit matches the painted font.
QFont font = themeManager->appFont(AppFont::InterfaceDefault);
font.setBold(widget->font().bold());
font.setPointSizeF(widget->font().pointSizeF());
const QRect widgetRect = widget->contentsRect(); const QRect widgetRect = widget->contentsRect();
const float widgetWidth = widgetRect.width(); const float widgetWidth = widgetRect.width();
const float widgetHeight = widgetRect.height(); const float widgetHeight = widgetRect.height();

View file

@ -1,5 +1,6 @@
#include "dynamic_font_size_push_button.h" #include "dynamic_font_size_push_button.h"
#include "../../interface/theme_manager.h"
#include "dynamic_font_size_label.h" #include "dynamic_font_size_label.h"
#include <QBrush> #include <QBrush>
@ -20,10 +21,12 @@ void DynamicFontSizePushButton::paintEvent(QPaintEvent *event)
// Call the base class paintEvent to preserve any other painting behavior // Call the base class paintEvent to preserve any other painting behavior
QPushButton::paintEvent(event); QPushButton::paintEvent(event);
// Adjust the font size dynamically based on the text // Adjust the font size dynamically based on the text. Family follows the
QFont newFont = font(); // InterfaceDefault theme role; the fitted size stays computed as before.
QFont newFont = themeManager->appFont(AppFont::InterfaceDefault);
float fontSize = DynamicFontSizeLabel::getWidgetMaximumFontSize(this, this->text()); float fontSize = DynamicFontSizeLabel::getWidgetMaximumFontSize(this, this->text());
newFont.setPointSizeF(fontSize); newFont.setPointSizeF(fontSize);
newFont.setBold(font().bold());
setFont(newFont); setFont(newFont);
// Get painter for custom painting // Get painter for custom painting

View file

@ -1,6 +1,9 @@
#include "home_styled_button.h" #include "home_styled_button.h"
#include "../../theme_manager.h"
#include <QBrush> #include <QBrush>
#include <QFont>
#include <QFontMetrics> #include <QFontMetrics>
#include <QPainter> #include <QPainter>
#include <QPainterPath> #include <QPainterPath>
@ -21,6 +24,18 @@ HomeStyledButton::HomeStyledButton(const QString &text, QPair<QColor, QColor> _g
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
setMinimumHeight(25); setMinimumHeight(25);
setStyleSheet(generateButtonStylesheet(gradientColors)); setStyleSheet(generateButtonStylesheet(gradientColors));
applyInterfaceDefaultFont();
connect(themeManager, &ThemeManager::fontsChanged, this, &HomeStyledButton::applyInterfaceDefaultFont);
}
void HomeStyledButton::applyInterfaceDefaultFont()
{
// Family comes from the InterfaceDefault theme role; the 34px size and
// bold weight previously carried by the stylesheet stay caller-side.
QFont buttonFont = themeManager->appFont(AppFont::InterfaceDefault);
buttonFont.setPixelSize(34);
buttonFont.setWeight(QFont::Bold);
setFont(buttonFont);
} }
void HomeStyledButton::updateStylesheet(const QPair<QColor, QColor> &colors) void HomeStyledButton::updateStylesheet(const QPair<QColor, QColor> &colors)
@ -50,9 +65,6 @@ QString HomeStyledButton::generateButtonStylesheet(const QPair<QColor, QColor> &
return QString(R"( return QString(R"(
QPushButton { QPushButton {
font-size: 34px;
font-weight: bold;
font-family: sans-serif, "Segoe UI", "Helvetica Neue";
padding: 30px; padding: 30px;
color: white; color: white;
border: 2px solid %1; border: 2px solid %1;

View file

@ -24,6 +24,9 @@ public:
public slots: public slots:
void paintEvent(QPaintEvent *event) override; void paintEvent(QPaintEvent *event) override;
private slots:
void applyInterfaceDefaultFont();
private: private:
QPair<QColor, QColor> gradientColors; QPair<QColor, QColor> gradientColors;
}; };

View file

@ -289,7 +289,6 @@ QGroupBox *HomeWidget::createButtons()
QGroupBox *box = new QGroupBox(this); QGroupBox *box = new QGroupBox(this);
box->setStyleSheet(R"( box->setStyleSheet(R"(
QGroupBox { QGroupBox {
font-size: 20px;
color: white; /* Title text color */ color: white; /* Title text color */
background: transparent; background: transparent;
} }
@ -300,6 +299,14 @@ QGroupBox *HomeWidget::createButtons()
subcontrol-position: top center; /* or top left / right */ subcontrol-position: top center; /* or top left / right */
} }
)"); )");
// Family comes from the InterfaceDefault theme role; the 20px size stays caller-side.
auto applyBoxFont = [box]() {
QFont boxFont = themeManager->appFont(AppFont::InterfaceDefault);
boxFont.setPixelSize(20);
box->setFont(boxFont);
};
applyBoxFont();
connect(themeManager, &ThemeManager::fontsChanged, this, applyBoxFont);
box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
QVBoxLayout *boxLayout = new QVBoxLayout; QVBoxLayout *boxLayout = new QVBoxLayout;
boxLayout->setAlignment(Qt::AlignHCenter); boxLayout->setAlignment(Qt::AlignHCenter);
@ -463,7 +470,9 @@ void HomeWidget::paintEvent(QPaintEvent *event)
constexpr int margin = 15; constexpr int margin = 15;
constexpr qreal attributionMargin = 4.0; constexpr qreal attributionMargin = 4.0;
QFont attributionFont = painter.font(); // Family comes from the InterfaceDefault theme role; the 14px sizes and
// bold weights stay caller-side as before.
QFont attributionFont = themeManager->appFont(AppFont::InterfaceDefault);
attributionFont.setPointSize(14); attributionFont.setPointSize(14);
attributionFont.setBold(true); attributionFont.setBold(true);
painter.setFont(attributionFont); painter.setFont(attributionFont);
@ -476,7 +485,7 @@ void HomeWidget::paintEvent(QPaintEvent *event)
// Card name bubble above the attribution (when enabled). // Card name bubble above the attribution (when enabled).
if (!cardName.isEmpty() && SettingsCache::instance().appearance().getHomeTabDisplayCardName()) { if (!cardName.isEmpty() && SettingsCache::instance().appearance().getHomeTabDisplayCardName()) {
QFont font = painter.font(); QFont font = themeManager->appFont(AppFont::InterfaceDefault);
font.setPointSize(14); font.setPointSize(14);
font.setBold(true); font.setBold(true);
painter.setFont(font); painter.setFont(font);

View file

@ -1,5 +1,6 @@
#include "first_run_wizard.h" #include "first_run_wizard.h"
#include "../interface/theme_manager.h"
#include "first_run_wizard_page.h" #include "first_run_wizard_page.h"
#include "pages/account_setup_page.h" #include "pages/account_setup_page.h"
#include "pages/card_database_setup_page.h" #include "pages/card_database_setup_page.h"
@ -32,9 +33,8 @@ FirstRunWizard::FirstRunWizard(QWidget *parent) : QDialog(parent)
bannerHost = new BannerHost(this); bannerHost = new BannerHost(this);
titleLabel = new QLabel(this); titleLabel = new QLabel(this);
QFont titleFont = QtUtils::boldFont(titleLabel->font()); applyInterfaceDefaultFont();
titleFont.setPointSizeF(titleFont.pointSizeF() * 1.4); connect(themeManager, &ThemeManager::fontsChanged, this, &FirstRunWizard::applyInterfaceDefaultFont);
titleLabel->setFont(titleFont);
subtitleLabel = new QLabel(this); subtitleLabel = new QLabel(this);
subtitleLabel->setWordWrap(true); subtitleLabel->setWordWrap(true);
@ -154,6 +154,16 @@ void FirstRunWizard::updateChrome()
} }
} }
void FirstRunWizard::applyInterfaceDefaultFont()
{
// Family comes from the InterfaceDefault theme role; the *1.4 size
// multiplier and bold weight stay caller-side as before.
QFont titleFont = themeManager->appFont(AppFont::InterfaceDefault);
titleFont.setPointSizeF(titleFont.pointSizeF() * 1.4);
titleFont.setBold(true);
titleLabel->setFont(titleFont);
}
void FirstRunWizard::goNext() void FirstRunWizard::goNext()
{ {
FirstRunWizardPage *page = pages[currentIndex]; FirstRunWizardPage *page = pages[currentIndex];

View file

@ -55,6 +55,7 @@ private slots:
void goBack(); void goBack();
void skip(); void skip();
void updateChrome(); void updateChrome();
void applyInterfaceDefaultFont();
private: private:
void addPage(FirstRunWizardPage *page); void addPage(FirstRunWizardPage *page);

View file

@ -1,5 +1,6 @@
#include "all_zones_card_amount_widget.h" #include "all_zones_card_amount_widget.h"
#include "../../theme_manager.h"
#include "../general/display/shadow_background_label.h" #include "../general/display/shadow_background_label.h"
#include "card_amount_widget.h" #include "card_amount_widget.h"
#include "libcockatrice/card/card_info.h" #include "libcockatrice/card/card_info.h"
@ -67,6 +68,7 @@ AllZonesCardAmountWidget::AllZonesCardAmountWidget(QWidget *parent,
buttonBoxSideboard->setVisible(!isToken); buttonBoxSideboard->setVisible(!isToken);
connect(cardSizeSlider, &QSlider::valueChanged, this, &AllZonesCardAmountWidget::adjustFontSize); connect(cardSizeSlider, &QSlider::valueChanged, this, &AllZonesCardAmountWidget::adjustFontSize);
connect(themeManager, &ThemeManager::fontsChanged, this, &AllZonesCardAmountWidget::applyCardTextFont);
QTimer::singleShot(10, this, [this]() { adjustFontSize(this->cardSizeSlider->value()); }); QTimer::singleShot(10, this, [this]() { adjustFontSize(this->cardSizeSlider->value()); });
@ -90,8 +92,9 @@ void AllZonesCardAmountWidget::adjustFontSize(int scalePercentage)
int newFontSize = minFontSize + (scalePercentage - basePercentage) * (maxFontSize - minFontSize) / 225; int newFontSize = minFontSize + (scalePercentage - basePercentage) * (maxFontSize - minFontSize) / 225;
newFontSize = std::clamp(newFontSize, minFontSize, maxFontSize); newFontSize = std::clamp(newFontSize, minFontSize, maxFontSize);
// Update the font labels // Update the font labels. The family comes from the CardText theme
QFont zoneLabelFont = zoneLabelMainboard->font(); // role; the slider-driven point size stays as before.
QFont zoneLabelFont = themeManager->appFont(AppFont::CardText);
zoneLabelFont.setPointSize(newFontSize); zoneLabelFont.setPointSize(newFontSize);
zoneLabelMainboard->setFont(zoneLabelFont); zoneLabelMainboard->setFont(zoneLabelFont);
zoneLabelSideboard->setFont(zoneLabelFont); zoneLabelSideboard->setFont(zoneLabelFont);
@ -101,6 +104,11 @@ void AllZonesCardAmountWidget::adjustFontSize(int scalePercentage)
repaint(); repaint();
} }
void AllZonesCardAmountWidget::applyCardTextFont()
{
adjustFontSize(cardSizeSlider->value());
}
void AllZonesCardAmountWidget::setAmounts(int mainboardAmount, int sideboardAmount, int tokensboardAmount) void AllZonesCardAmountWidget::setAmounts(int mainboardAmount, int sideboardAmount, int tokensboardAmount)
{ {
buttonBoxMainboard->setAmount(mainboardAmount); buttonBoxMainboard->setAmount(mainboardAmount);

View file

@ -36,6 +36,9 @@ public slots:
void adjustFontSize(int scalePercentage); void adjustFontSize(int scalePercentage);
void setAmounts(int mainboardAmount, int sideboardAmount, int tokensboardAmount); void setAmounts(int mainboardAmount, int sideboardAmount, int tokensboardAmount);
private slots:
void applyCardTextFont();
private: private:
QVBoxLayout *layout; QVBoxLayout *layout;
QSlider *cardSizeSlider; QSlider *cardSizeSlider;

View file

@ -1,5 +1,6 @@
#include "card_amount_widget.h" #include "card_amount_widget.h"
#include "../../theme_manager.h"
#include "../deck_editor/deck_state_manager.h" #include "../deck_editor/deck_state_manager.h"
#include "../general/display/dynamic_font_size_push_button.h" #include "../general/display/dynamic_font_size_push_button.h"
#include "deck_list_model.h" #include "deck_list_model.h"
@ -77,6 +78,7 @@ CardAmountWidget::CardAmountWidget(QWidget *parent,
// Connect slider for dynamic font size adjustment // Connect slider for dynamic font size adjustment
connect(cardSizeSlider, &QSlider::valueChanged, this, &CardAmountWidget::adjustFontSize); connect(cardSizeSlider, &QSlider::valueChanged, this, &CardAmountWidget::adjustFontSize);
connect(themeManager, &ThemeManager::fontsChanged, this, &CardAmountWidget::applyCardTextFont);
} }
int CardAmountWidget::getAmount() int CardAmountWidget::getAmount()
@ -138,8 +140,9 @@ void CardAmountWidget::adjustFontSize(int scalePercentage)
int newFontSize = minFontSize + (scalePercentage - basePercentage) * (maxFontSize - minFontSize) / 225; int newFontSize = minFontSize + (scalePercentage - basePercentage) * (maxFontSize - minFontSize) / 225;
newFontSize = std::clamp(newFontSize, minFontSize, maxFontSize); newFontSize = std::clamp(newFontSize, minFontSize, maxFontSize);
// Update the font for card count label // Update the font for card count label. The family comes from the
QFont cardCountFont = cardCountInZone->font(); // CardText theme role; the slider-driven point size stays as before.
QFont cardCountFont = themeManager->appFont(AppFont::CardText);
cardCountFont.setPointSize(newFontSize); cardCountFont.setPointSize(newFontSize);
cardCountInZone->setFont(cardCountFont); cardCountInZone->setFont(cardCountFont);
@ -150,6 +153,11 @@ void CardAmountWidget::adjustFontSize(int scalePercentage)
repaint(); repaint();
} }
void CardAmountWidget::applyCardTextFont()
{
adjustFontSize(cardSizeSlider->value());
}
/** /**
* @brief Updates the card count display in the widget. * @brief Updates the card count display in the widget.
*/ */

View file

@ -67,6 +67,7 @@ private slots:
void removePrintingSideboard(); void removePrintingSideboard();
void removePrintingTokensboard(); void removePrintingTokensboard();
void adjustFontSize(int scalePercentage); void adjustFontSize(int scalePercentage);
void applyCardTextFont();
}; };
#endif // CARD_AMOUNT_WIDGET_H #endif // CARD_AMOUNT_WIDGET_H

View file

@ -1,5 +1,8 @@
#include "printing_selector_placeholder_widget.h" #include "printing_selector_placeholder_widget.h"
#include "../interface/theme_manager.h"
#include <QFont>
#include <QLabel> #include <QLabel>
#include <QSizePolicy> #include <QSizePolicy>
#include <QVBoxLayout> #include <QVBoxLayout>
@ -30,10 +33,12 @@ PrintingSelectorPlaceholderWidget::PrintingSelectorPlaceholderWidget(QWidget *pa
textLabel->setStyleSheet(R"( textLabel->setStyleSheet(R"(
QLabel { QLabel {
color: palette(mid); color: palette(mid);
font-size: 14px;
padding: 10px; padding: 10px;
} }
)"); )");
applyInterfaceDefaultFont();
connect(themeManager, &ThemeManager::fontsChanged, this,
&PrintingSelectorPlaceholderWidget::applyInterfaceDefaultFont);
mainLayout->addWidget(imageLabel); mainLayout->addWidget(imageLabel);
mainLayout->addWidget(textLabel); mainLayout->addWidget(textLabel);
@ -44,4 +49,12 @@ PrintingSelectorPlaceholderWidget::PrintingSelectorPlaceholderWidget(QWidget *pa
void PrintingSelectorPlaceholderWidget::retranslateUi() void PrintingSelectorPlaceholderWidget::retranslateUi()
{ {
textLabel->setText(tr("Select a card to view its available printings")); textLabel->setText(tr("Select a card to view its available printings"));
}
void PrintingSelectorPlaceholderWidget::applyInterfaceDefaultFont()
{
// Family comes from the InterfaceDefault theme role; the 14px size stays caller-side.
QFont placeholderFont = themeManager->appFont(AppFont::InterfaceDefault);
placeholderFont.setPixelSize(14);
textLabel->setFont(placeholderFont);
} }

View file

@ -14,6 +14,9 @@ class PrintingSelectorPlaceholderWidget : public QWidget
public: public:
explicit PrintingSelectorPlaceholderWidget(QWidget *parent = nullptr); explicit PrintingSelectorPlaceholderWidget(QWidget *parent = nullptr);
private slots:
void applyInterfaceDefaultFont();
private: private:
QVBoxLayout *mainLayout; QVBoxLayout *mainLayout;
QLabel *imageLabel; QLabel *imageLabel;

View file

@ -4,6 +4,7 @@
#include "../../client/sound_engine.h" #include "../../client/sound_engine.h"
#include "../../interface/card_localization.h" #include "../../interface/card_localization.h"
#include "../../interface/pixel_map_generator.h" #include "../../interface/pixel_map_generator.h"
#include "../../interface/theme_manager.h"
#include "../../tabs/tab_supervisor.h" #include "../../tabs/tab_supervisor.h"
#include "../user/user_context_menu.h" #include "../user/user_context_menu.h"
#include "../user/user_list_manager.h" #include "../user/user_list_manager.h"
@ -63,6 +64,9 @@ ChatView::ChatView(TabSupervisor *_tabSupervisor, AbstractGame *_game, bool _sho
connect(&SettingsCache::instance(), &SettingsCache::themeChanged, this, &ChatView::adjustColorsToPalette); connect(&SettingsCache::instance(), &SettingsCache::themeChanged, this, &ChatView::adjustColorsToPalette);
applyChatFont();
connect(themeManager, &ThemeManager::fontsChanged, this, &ChatView::applyChatFont);
userContextMenu = new UserContextMenu(tabSupervisor, this, game); userContextMenu = new UserContextMenu(tabSupervisor, this, game);
connect(userContextMenu, SIGNAL(openMessageDialog(QString, bool)), this, SIGNAL(openMessageDialog(QString, bool))); connect(userContextMenu, SIGNAL(openMessageDialog(QString, bool)), this, SIGNAL(openMessageDialog(QString, bool)));
@ -108,6 +112,16 @@ void ChatView::adjustColorsToPalette()
QTimer::singleShot(0, this, &ChatView::refreshBlockColors); QTimer::singleShot(0, this, &ChatView::refreshBlockColors);
} }
void ChatView::applyChatFont()
{
// Family comes from the ChatLog theme role; the message char formats carry
// no family of their own, so they (and new content) follow the widget and
// document default fonts. Weights/italics stay caller-side as before.
const QFont chatFont = themeManager->appFont(AppFont::ChatLog);
setFont(chatFont);
document()->setDefaultFont(chatFont);
}
void ChatView::refreshBlockColors() void ChatView::refreshBlockColors()
{ {
QTextDocument *doc = document(); QTextDocument *doc = document();

View file

@ -111,6 +111,7 @@ private slots:
void openLink(const QUrl &link); void openLink(const QUrl &link);
void actMessageClicked(); void actMessageClicked();
void adjustColorsToPalette(); void adjustColorsToPalette();
void applyChatFont();
void refreshBlockColors(); void refreshBlockColors();
void onScrollBarRangeChanged(); void onScrollBarRangeChanged();
void onScrollBarValueChanged(int value); void onScrollBarValueChanged(int value);

View file

@ -1,6 +1,7 @@
#include "user_info_box.h" #include "user_info_box.h"
#include "../../interface/pixel_map_generator.h" #include "../../interface/pixel_map_generator.h"
#include "../../interface/theme_manager.h"
#include "../../interface/widgets/dialogs/dlg_edit_avatar.h" #include "../../interface/widgets/dialogs/dlg_edit_avatar.h"
#include "../../interface/widgets/dialogs/dlg_edit_password.h" #include "../../interface/widgets/dialogs/dlg_edit_password.h"
#include "../../interface/widgets/dialogs/dlg_edit_user.h" #include "../../interface/widgets/dialogs/dlg_edit_user.h"
@ -39,10 +40,8 @@
UserInfoBox::UserInfoBox(AbstractClient *_client, bool _editable, QWidget *parent, Qt::WindowFlags flags) UserInfoBox::UserInfoBox(AbstractClient *_client, bool _editable, QWidget *parent, Qt::WindowFlags flags)
: QWidget(parent, flags), client(_client), editable(_editable) : QWidget(parent, flags), client(_client), editable(_editable)
{ {
QFont nameFont = nameLabel.font(); applyInterfaceDefaultFont();
nameFont.setBold(true); connect(themeManager, &ThemeManager::fontsChanged, this, &UserInfoBox::applyInterfaceDefaultFont);
nameFont.setPointSizeF(nameFont.pointSizeF() * 1.5);
nameLabel.setFont(nameFont);
auto *userIconAndNameLayout = new QHBoxLayout; auto *userIconAndNameLayout = new QHBoxLayout;
userIconAndNameLayout->addWidget(&userLevelIcon, 0, Qt::AlignCenter); userIconAndNameLayout->addWidget(&userLevelIcon, 0, Qt::AlignCenter);
@ -91,6 +90,16 @@ UserInfoBox::UserInfoBox(AbstractClient *_client, bool _editable, QWidget *paren
retranslateUi(); retranslateUi();
} }
void UserInfoBox::applyInterfaceDefaultFont()
{
// Family comes from the InterfaceDefault theme role; the *1.5 size
// multiplier and bold weight stay caller-side as before.
QFont nameFont = themeManager->appFont(AppFont::InterfaceDefault);
nameFont.setBold(true);
nameFont.setPointSizeF(nameFont.pointSizeF() * 1.5);
nameLabel.setFont(nameFont);
}
void UserInfoBox::retranslateUi() void UserInfoBox::retranslateUi()
{ {
realNameLabel1.setText(tr("Real Name:")); realNameLabel1.setText(tr("Real Name:"));

View file

@ -48,6 +48,7 @@ private slots:
void processEditResponse(const Response &r); void processEditResponse(const Response &r);
void processPasswordResponse(const Response &r); void processPasswordResponse(const Response &r);
void processAvatarResponse(const Response &r); void processAvatarResponse(const Response &r);
void applyInterfaceDefaultFont();
void actEdit(); void actEdit();
void actEditInternal(const Response &r); void actEditInternal(const Response &r);

View file

@ -142,7 +142,8 @@ public:
p->drawEllipse(QRectF(rect.left() + 9, rect.top() + (rect.height() - 8) / 2.0, 8, 8)); p->drawEllipse(QRectF(rect.left() + 9, rect.top() + (rect.height() - 8) / 2.0, 8, 8));
// Game title (bold, elided) // Game title (bold, elided)
QFont tf = option.font; // Family comes from the InterfaceDefault theme role; bold stays caller-side.
QFont tf = themeManager->appFont(AppFont::InterfaceDefault);
tf.setBold(true); tf.setBold(true);
p->setFont(tf); p->setFont(tf);
p->setPen(pal.color(QPalette::Text)); p->setPen(pal.color(QPalette::Text));
@ -154,7 +155,7 @@ public:
// Player count // Player count
const bool full = game.player_count() >= game.max_players(); const bool full = game.player_count() >= game.max_players();
p->setFont(option.font); p->setFont(themeManager->appFont(AppFont::InterfaceDefault));
p->setPen(full ? QColor(249, 115, 22) : pal.color(QPalette::Disabled, QPalette::Text)); p->setPen(full ? QColor(249, 115, 22) : pal.color(QPalette::Disabled, QPalette::Text));
p->drawText(QRect(rect.right() - countW - 4, rect.top(), countW, rect.height()), p->drawText(QRect(rect.right() - countW - 4, rect.top(), countW, rect.height()),
Qt::AlignVCenter | Qt::AlignRight, Qt::AlignVCenter | Qt::AlignRight,
@ -323,7 +324,8 @@ void UserInfoHeaderWidget::paintEvent(QPaintEvent *)
const int tx = ax + AvatarSize + AvatarToTextGap; const int tx = ax + AvatarSize + AvatarToTextGap;
const int tw = rect.width() - tx - 8; const int tw = rect.width() - tx - 8;
QFont nf = font(); // Family comes from the InterfaceDefault theme role; the *1.12 size and bold stay caller-side.
QFont nf = themeManager->appFont(AppFont::InterfaceDefault);
nf.setBold(true); nf.setBold(true);
nf.setPointSizeF(nf.pointSizeF() * 1.12); nf.setPointSizeF(nf.pointSizeF() * 1.12);
p.setFont(nf); p.setFont(nf);
@ -352,7 +354,7 @@ void UserInfoHeaderWidget::paintEvent(QPaintEvent *)
} }
if (!badge.text.isEmpty()) { if (!badge.text.isEmpty()) {
QFont bf = font(); QFont bf = themeManager->appFont(AppFont::InterfaceDefault);
bf.setPointSizeF(bf.pointSizeF() * 0.70); bf.setPointSizeF(bf.pointSizeF() * 0.70);
bf.setBold(true); bf.setBold(true);
p.setFont(bf); p.setFont(bf);
@ -369,7 +371,8 @@ void UserInfoHeaderWidget::paintEvent(QPaintEvent *)
// The painter font at this point depends on whether a badge was drawn // The painter font at this point depends on whether a badge was drawn
// (badge font vs username font), so pin an explicit font for the pill. // (badge font vs username font), so pin an explicit font for the pill.
p.setFont(font()); // Family follows the InterfaceDefault theme role like the rest of the popup chrome.
p.setFont(themeManager->appFont(AppFont::InterfaceDefault));
// Only show the attribution when there is actually art on screen: on a // Only show the attribution when there is actually art on screen: on a
// cache miss cardArt is null and the pill would float over the plain // cache miss cardArt is null and the pill would float over the plain
@ -398,6 +401,7 @@ UserInfoPopup::UserInfoPopup(TabSupervisor *_ts,
// Restyle the popup chrome when the theme or its color scheme changes. // Restyle the popup chrome when the theme or its color scheme changes.
if (themeManager) { if (themeManager) {
connect(themeManager, &ThemeManager::themeChanged, this, &UserInfoPopup::applyTheme); connect(themeManager, &ThemeManager::themeChanged, this, &UserInfoPopup::applyTheme);
connect(themeManager, &ThemeManager::fontsChanged, this, &UserInfoPopup::applyTheme);
} }
} }
@ -475,15 +479,24 @@ void UserInfoPopup::applyTheme()
separator->setStyleSheet(QStringLiteral("color:%1; margin: 0 8px;").arg(colorStr(theme.border))); separator->setStyleSheet(QStringLiteral("color:%1; margin: 0 8px;").arg(colorStr(theme.border)));
gamesLabel->setStyleSheet(QStringLiteral("color:%1; font-size:11px; font-weight:bold; background:transparent;") gamesLabel->setStyleSheet(QStringLiteral("color:%1; background:transparent;").arg(colorStr(theme.subText)));
.arg(colorStr(theme.subText))); QFont gamesLabelFont = themeManager->appFont(AppFont::InterfaceDefault);
gamesLabelFont.setPixelSize(11);
gamesLabelFont.setWeight(QFont::Bold);
gamesLabel->setFont(gamesLabelFont);
refreshBtn->setStyleSheet(QStringLiteral("QPushButton{color:%1;border:none;font-size:14px;background:transparent;}" refreshBtn->setStyleSheet(QStringLiteral("QPushButton{color:%1;border:none;background:transparent;}"
"QPushButton:hover{color:%2;}") "QPushButton:hover{color:%2;}")
.arg(colorStr(theme.subText), colorStr(theme.text))); .arg(colorStr(theme.subText), colorStr(theme.text)));
QFont refreshFont = themeManager->appFont(AppFont::InterfaceDefault);
refreshFont.setPixelSize(14);
refreshBtn->setFont(refreshFont);
gamesStatus->setStyleSheet(QStringLiteral("color:%1; font-size:11px; padding:10px; background:transparent;") gamesStatus->setStyleSheet(
.arg(colorStr(theme.statusText))); QStringLiteral("color:%1; padding:10px; background:transparent;").arg(colorStr(theme.statusText)));
QFont statusFont = themeManager->appFont(AppFont::InterfaceDefault);
statusFont.setPixelSize(11);
gamesStatus->setFont(statusFont);
gamesView->setStyleSheet(QStringLiteral("QListView{background:%1;border:none;}" gamesView->setStyleSheet(QStringLiteral("QListView{background:%1;border:none;}"
"QListView::item:selected{background:%2;}") "QListView::item:selected{background:%2;}")
@ -491,9 +504,12 @@ void UserInfoPopup::applyTheme()
closeBtn->setStyleSheet( closeBtn->setStyleSheet(
QStringLiteral("QPushButton{background:%1;color:%2;" QStringLiteral("QPushButton{background:%1;color:%2;"
"border:none;border-radius:11px;font-size:10px;}" "border:none;border-radius:11px;}"
"QPushButton:hover{color:%3;background:%4;}") "QPushButton:hover{color:%3;background:%4;}")
.arg(colorStr(theme.closeBg), colorStr(theme.subText), colorStr(theme.text), colorStr(theme.closeHover))); .arg(colorStr(theme.closeBg), colorStr(theme.subText), colorStr(theme.text), colorStr(theme.closeHover)));
QFont closeFont = themeManager->appFont(AppFont::InterfaceDefault);
closeFont.setPixelSize(10);
closeBtn->setFont(closeFont);
header->update(); header->update();
} }
@ -508,13 +524,17 @@ static QPushButton *makeBtn(const QString &label, const QString &tip, QWidget *p
b->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); b->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
b->setStyleSheet(QStringLiteral("QPushButton{" b->setStyleSheet(QStringLiteral("QPushButton{"
" background:%1;color:%2;border:1px solid %3;" " background:%1;color:%2;border:1px solid %3;"
" border-radius:4px;font-size:11px;padding:0 4px;" " border-radius:4px;padding:0 4px;"
"}" "}"
"QPushButton:hover{background:%4;color:%5;}" "QPushButton:hover{background:%4;color:%5;}"
"QPushButton:pressed{background:%6;}" "QPushButton:pressed{background:%6;}"
"QPushButton:disabled{color:%7;border-color:%3;}") "QPushButton:disabled{color:%7;border-color:%3;}")
.arg(colorStr(t.buttonBg), colorStr(t.text), colorStr(t.buttonBorder), colorStr(t.buttonHover), .arg(colorStr(t.buttonBg), colorStr(t.text), colorStr(t.buttonBorder), colorStr(t.buttonHover),
colorStr(t.text), colorStr(t.buttonPressed), colorStr(t.buttonDisabled))); colorStr(t.text), colorStr(t.buttonPressed), colorStr(t.buttonDisabled)));
// Family comes from the InterfaceDefault theme role; the 11px size stays caller-side.
QFont btnFont = themeManager->appFont(AppFont::InterfaceDefault);
btnFont.setPixelSize(11);
b->setFont(btnFont);
return b; return b;
} }

View file

@ -1,6 +1,7 @@
#include "user_list_painter.h" #include "user_list_painter.h"
#include "../../interface/pixel_map_generator.h" #include "../../interface/pixel_map_generator.h"
#include "../../interface/theme_manager.h"
#include "libcockatrice/protocol/pb/serverinfo_user.pb.h" #include "libcockatrice/protocol/pb/serverinfo_user.pb.h"
#include <QAbstractScrollArea> #include <QAbstractScrollArea>
@ -292,7 +293,9 @@ void UserListPainter::drawUserName(QPainter *painter,
bool online, bool online,
const Style &style) const Style &style)
{ {
QFont nameFont = option.font; Q_UNUSED(option);
// Family comes from the InterfaceDefault theme role; bold stays caller-side.
QFont nameFont = themeManager->appFont(AppFont::InterfaceDefault);
nameFont.setBold(true); nameFont.setBold(true);
painter->setFont(nameFont); painter->setFont(nameFont);
@ -351,7 +354,9 @@ void UserListPainter::drawBadges(QPainter *painter,
return; return;
} }
QFont badgeFont = option.font; Q_UNUSED(option);
// Family comes from the InterfaceDefault theme role; the *0.68 size and bold stay caller-side.
QFont badgeFont = themeManager->appFont(AppFont::InterfaceDefault);
badgeFont.setPointSizeF(badgeFont.pointSizeF() * 0.68); badgeFont.setPointSizeF(badgeFont.pointSizeF() * 0.68);
badgeFont.setBold(true); badgeFont.setBold(true);
painter->setFont(badgeFont); painter->setFont(badgeFont);

View file

@ -519,6 +519,19 @@ UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor,
userTree->viewport()->update(); userTree->viewport()->update();
userTree->update(); userTree->update();
}); });
// Divider headers store their font on the item, so re-apply the role
// family here; the painter reads the role live and needs only the
// repaint below.
connect(themeManager, &ThemeManager::fontsChanged, this, [this] {
QFont dividerFont = themeManager->appFont(AppFont::InterfaceDefault);
dividerFont.setBold(true);
for (auto it = sectionItems.constBegin(); it != sectionItems.constEnd(); ++it) {
it.value()->setFont(0, dividerFont);
it.value()->setSizeHint(0, QSize(0, QFontMetrics(dividerFont).height() + 16));
}
userTree->viewport()->update();
userTree->update();
});
} }
QVBoxLayout *vbox = new QVBoxLayout; QVBoxLayout *vbox = new QVBoxLayout;
@ -1492,7 +1505,7 @@ QTreeWidgetItem *UserListWidget::createSectionItem(Section section)
// They act as collapsible section headers once they have focus. // They act as collapsible section headers once they have focus.
divider->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); divider->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
QFont font = userTree->font(); QFont font = themeManager->appFont(AppFont::InterfaceDefault);
font.setBold(true); font.setBold(true);
divider->setFont(0, font); divider->setFont(0, font);
// A little taller than a plain text row so the header reads as a section // A little taller than a plain text row so the header reads as a section

View file

@ -5,6 +5,7 @@
*/ */
#include "settings_search_delegate.h" #include "settings_search_delegate.h"
#include "../interface/theme_manager.h"
#include "settings_search_model.h" #include "settings_search_model.h"
#include <QBrush> #include <QBrush>
@ -88,7 +89,8 @@ void SettingsSearchDelegate::paint(QPainter *painter,
QRect textRect = contentRect.adjusted(iconOffset, 0, 0, 0); QRect textRect = contentRect.adjusted(iconOffset, 0, 0, 0);
// Breadcrumb: "Page > Group" // Breadcrumb: "Page > Group"
QFont breadcrumbFont = option.font; // Family comes from the InterfaceDefault theme role; the -1 size and bold stay caller-side.
QFont breadcrumbFont = themeManager->appFont(AppFont::InterfaceDefault);
breadcrumbFont.setPointSize(breadcrumbFont.pointSize() - 1); breadcrumbFont.setPointSize(breadcrumbFont.pointSize() - 1);
breadcrumbFont.setBold(true); breadcrumbFont.setBold(true);
@ -113,7 +115,7 @@ void SettingsSearchDelegate::paint(QPainter *painter,
yPos += 20; yPos += 20;
// Setting label // Setting label
QFont labelFont = option.font; QFont labelFont = themeManager->appFont(AppFont::InterfaceDefault);
labelFont.setPointSize(labelFont.pointSize() + 1); labelFont.setPointSize(labelFont.pointSize() + 1);
labelFont.setBold(isSelected); labelFont.setBold(isSelected);

View file

@ -1,5 +1,6 @@
#include "archidekt_deck_preview_image_display_widget.h" #include "archidekt_deck_preview_image_display_widget.h"
#include "../../../../../theme_manager.h"
#include "../../../../general/display/shadow_background_label.h" #include "../../../../general/display/shadow_background_label.h"
#include <QFont> #include <QFont>
@ -17,13 +18,9 @@ ArchidektDeckPreviewImageDisplayWidget::ArchidektDeckPreviewImageDisplayWidget(Q
bottomLeftLabel = new ShadowBackgroundLabel(this, ""); bottomLeftLabel = new ShadowBackgroundLabel(this, "");
bottomRightLabel = new ShadowBackgroundLabel(this, ""); bottomRightLabel = new ShadowBackgroundLabel(this, "");
QFont f; applyCardTextFont();
f.setBold(true); connect(themeManager, &ThemeManager::fontsChanged, this,
f.setPointSize(16); &ArchidektDeckPreviewImageDisplayWidget::applyCardTextFont);
topLeftLabel->setFont(f);
topRightLabel->setFont(f);
bottomLeftLabel->setFont(f);
bottomRightLabel->setFont(f);
// Raise so labels appear above image // Raise so labels appear above image
topLeftLabel->raise(); topLeftLabel->raise();
@ -32,6 +29,18 @@ ArchidektDeckPreviewImageDisplayWidget::ArchidektDeckPreviewImageDisplayWidget(Q
bottomRightLabel->raise(); bottomRightLabel->raise();
} }
void ArchidektDeckPreviewImageDisplayWidget::applyCardTextFont()
{
// Family comes from the CardText theme role; size and weight stay caller-side as before.
QFont f = themeManager->appFont(AppFont::CardText);
f.setBold(true);
f.setPointSize(16);
topLeftLabel->setFont(f);
topRightLabel->setFont(f);
bottomLeftLabel->setFont(f);
bottomRightLabel->setFont(f);
}
void ArchidektDeckPreviewImageDisplayWidget::setAspectRatio(float ratio) void ArchidektDeckPreviewImageDisplayWidget::setAspectRatio(float ratio)
{ {
aspectRatio = ratio; aspectRatio = ratio;

View file

@ -60,6 +60,9 @@ protected:
*/ */
void resizeEvent(QResizeEvent *event) override; void resizeEvent(QResizeEvent *event) override;
private slots:
void applyCardTextFont();
private: private:
float aspectRatio = 1.0f; ///< Aspect ratio to maintain for the preview image float aspectRatio = 1.0f; ///< Aspect ratio to maintain for the preview image
}; };

View file

@ -1,5 +1,6 @@
#include "edhrec_api_response_card_details_display_widget.h" #include "edhrec_api_response_card_details_display_widget.h"
#include "../../../../../../theme_manager.h"
#include "../../../../../cards/card_info_picture_widget.h" #include "../../../../../cards/card_info_picture_widget.h"
#include "../../../../../cards/card_size_widget.h" #include "../../../../../cards/card_size_widget.h"
#include "../../../../../general/display/background_plate_widget.h" #include "../../../../../general/display/background_plate_widget.h"
@ -9,6 +10,7 @@
#include "edhrec_api_response_card_synergy_display_widget.h" #include "edhrec_api_response_card_synergy_display_widget.h"
#include "libcockatrice/card/database/card_database_querier.h" #include "libcockatrice/card/database/card_database_querier.h"
#include <QFont>
#include <QLabel> #include <QLabel>
#include <QMouseEvent> #include <QMouseEvent>
#include <QObject> #include <QObject>
@ -32,7 +34,9 @@ EdhrecApiResponseCardDetailsDisplayWidget::EdhrecApiResponseCardDetailsDisplayWi
nameLabel = new QLabel(this); nameLabel = new QLabel(this);
nameLabel->setText(toDisplay.name); nameLabel->setText(toDisplay.name);
nameLabel->setAlignment(Qt::AlignHCenter); nameLabel->setAlignment(Qt::AlignHCenter);
nameLabel->setStyleSheet("font-size: 20px; font-weight: bold"); applyCardTextFont();
connect(themeManager, &ThemeManager::fontsChanged, this,
&EdhrecApiResponseCardDetailsDisplayWidget::applyCardTextFont);
inclusionDisplayWidget = new EdhrecApiResponseCardInclusionDisplayWidget(this, toDisplay); inclusionDisplayWidget = new EdhrecApiResponseCardInclusionDisplayWidget(this, toDisplay);
@ -86,3 +90,14 @@ void EdhrecApiResponseCardDetailsDisplayWidget::actRequestPageNavigation()
{ {
emit requestUrl(toDisplay.url); emit requestUrl(toDisplay.url);
} }
void EdhrecApiResponseCardDetailsDisplayWidget::applyCardTextFont()
{
// Family comes from the CardText theme role; the 20px size and bold
// weight previously carried by the stylesheet stay caller-side. Pixel
// size, not point size: the stylesheet said 20px, and 20pt is ~27px.
QFont cardFont = themeManager->appFont(AppFont::CardText);
cardFont.setPixelSize(20);
cardFont.setWeight(QFont::Bold);
nameLabel->setFont(cardFont);
}

View file

@ -30,6 +30,9 @@ public slots:
signals: signals:
void requestUrl(QString url); void requestUrl(QString url);
private slots:
void applyCardTextFont();
private: private:
EdhrecApiResponseCardDetails toDisplay; EdhrecApiResponseCardDetails toDisplay;
QVBoxLayout *layout; QVBoxLayout *layout;

View file

@ -1,5 +1,6 @@
#include "edhrec_api_response_commander_details_display_widget.h" #include "edhrec_api_response_commander_details_display_widget.h"
#include "../../../../../../theme_manager.h"
#include "../../../../../cards/card_info_picture_widget.h" #include "../../../../../cards/card_info_picture_widget.h"
#include "../../../../../cards/card_size_widget.h" #include "../../../../../cards/card_size_widget.h"
#include "../../api_response/cards/edhrec_commander_api_response_commander_details.h" #include "../../api_response/cards/edhrec_commander_api_response_commander_details.h"
@ -8,6 +9,7 @@
#include "edhrec_commander_api_response_navigation_widget.h" #include "edhrec_commander_api_response_navigation_widget.h"
#include "libcockatrice/card/database/card_database_querier.h" #include "libcockatrice/card/database/card_database_querier.h"
#include <QFont>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QLabel> #include <QLabel>
#include <QObject> #include <QObject>
@ -48,15 +50,16 @@ EdhrecCommanderResponseCommanderDetailsDisplayWidget::EdhrecCommanderResponseCom
commanderName = new QLabel(this); commanderName = new QLabel(this);
commanderName->setText(commanderDetails.getName()); commanderName->setText(commanderDetails.getName());
commanderName->setAlignment(Qt::AlignCenter); commanderName->setAlignment(Qt::AlignCenter);
commanderName->setStyleSheet("font-size: 28px; font-weight: bold");
label = new QLabel(this); label = new QLabel(this);
label->setAlignment(Qt::AlignCenter); label->setAlignment(Qt::AlignCenter);
label->setStyleSheet("font-size: 16px");
salt = new QLabel(this); salt = new QLabel(this);
salt->setAlignment(Qt::AlignCenter); salt->setAlignment(Qt::AlignCenter);
salt->setStyleSheet("font-size: 16px");
applyCardTextFont();
connect(themeManager, &ThemeManager::fontsChanged, this,
&EdhrecCommanderResponseCommanderDetailsDisplayWidget::applyCardTextFont);
cardPricesDisplayWidget = new EdhrecApiResponseCardPricesDisplayWidget(this, commanderDetails.getPrices()); cardPricesDisplayWidget = new EdhrecApiResponseCardPricesDisplayWidget(this, commanderDetails.getPrices());
@ -83,3 +86,19 @@ void EdhrecCommanderResponseCommanderDetailsDisplayWidget::retranslateUi()
label->setText(commanderDetails.getLabel()); label->setText(commanderDetails.getLabel());
salt->setText(tr("Salt: ") + QString::number(commanderDetails.getSalt())); salt->setText(tr("Salt: ") + QString::number(commanderDetails.getSalt()));
} }
void EdhrecCommanderResponseCommanderDetailsDisplayWidget::applyCardTextFont()
{
// Family comes from the CardText theme role; the sizes and the bold
// weight previously carried by the stylesheets stay caller-side. Pixel
// sizes, not point sizes: the stylesheets said 28px and 16px.
QFont nameFont = themeManager->appFont(AppFont::CardText);
nameFont.setPixelSize(28);
nameFont.setWeight(QFont::Bold);
commanderName->setFont(nameFont);
QFont detailsFont = themeManager->appFont(AppFont::CardText);
detailsFont.setPixelSize(16);
label->setFont(detailsFont);
salt->setFont(detailsFont);
}

View file

@ -30,6 +30,9 @@ public:
QString baseUrl); QString baseUrl);
void retranslateUi(); void retranslateUi();
private slots:
void applyCardTextFont();
private: private:
EdhrecCommanderApiResponseCommanderDetails commanderDetails; EdhrecCommanderApiResponseCommanderDetails commanderDetails;
QHBoxLayout *layout; QHBoxLayout *layout;

View file

@ -1,6 +1,9 @@
#include "edhrec_commander_api_response_bracket_navigation_widget.h" #include "edhrec_commander_api_response_bracket_navigation_widget.h"
#include "../../interface/theme_manager.h"
#include <QChar> #include <QChar>
#include <QFont>
#include <QGridLayout> #include <QGridLayout>
#include <QLabel> #include <QLabel>
#include <QPushButton> #include <QPushButton>
@ -25,7 +28,7 @@ EdhrecCommanderApiResponseBracketNavigationWidget::EdhrecCommanderApiResponseBra
QString label = option.isEmpty() ? "All" : option.at(0).toUpper() + option.mid(1); QString label = option.isEmpty() ? "All" : option.at(0).toUpper() + option.mid(1);
QPushButton *optionButton = new QPushButton(label, this); QPushButton *optionButton = new QPushButton(label, this);
optionButton->setMinimumHeight(84); optionButton->setMinimumHeight(84);
optionButton->setStyleSheet("font-size: 24px"); optionButton->setStyleSheet(QString());
gameChangerButtons[option] = optionButton; gameChangerButtons[option] = optionButton;
layout->addWidget(optionButton, 2, i); layout->addWidget(optionButton, 2, i);
connect(optionButton, &QPushButton::clicked, this, [=, this]() { connect(optionButton, &QPushButton::clicked, this, [=, this]() {
@ -35,6 +38,10 @@ EdhrecCommanderApiResponseBracketNavigationWidget::EdhrecCommanderApiResponseBra
}); });
} }
applyInterfaceDefaultFont();
connect(themeManager, &ThemeManager::fontsChanged, this,
&EdhrecCommanderApiResponseBracketNavigationWidget::applyInterfaceDefaultFont);
updateOptionButtonSelection(gameChangerButtons, ""); updateOptionButtonSelection(gameChangerButtons, "");
retranslateUi(); retranslateUi();
@ -94,8 +101,18 @@ void EdhrecCommanderApiResponseBracketNavigationWidget::updateOptionButtonSelect
const QString &selectedKey) const QString &selectedKey)
{ {
for (auto it = buttons.begin(); it != buttons.end(); ++it) { for (auto it = buttons.begin(); it != buttons.end(); ++it) {
it.value()->setStyleSheet(it.key() == selectedKey const bool selected = (it.key() == selectedKey);
? "background-color: lightblue; font-weight: bold; font-size: 24px;" it.value()->setStyleSheet(selected ? QStringLiteral("background-color: lightblue;") : QString());
: "font-size: 24px"); QFont buttonFont = themeManager->appFont(AppFont::InterfaceDefault);
buttonFont.setPixelSize(24);
buttonFont.setWeight(selected ? QFont::Bold : QFont::Normal);
it.value()->setFont(buttonFont);
} }
}
void EdhrecCommanderApiResponseBracketNavigationWidget::applyInterfaceDefaultFont()
{
// Family comes from the InterfaceDefault theme role; the 24px size stays
// caller-side and the selected button keeps its bold emphasis.
updateOptionButtonSelection(gameChangerButtons, selectedGameChanger);
} }

View file

@ -37,6 +37,9 @@ private:
QMap<QString, QPushButton *> gameChangerButtons; QMap<QString, QPushButton *> gameChangerButtons;
void updateOptionButtonSelection(QMap<QString, QPushButton *> &buttons, const QString &selectedKey); void updateOptionButtonSelection(QMap<QString, QPushButton *> &buttons, const QString &selectedKey);
private slots:
void applyInterfaceDefaultFont();
}; };
#endif // COCKATRICE_EDHREC_COMMANDER_API_RESPONSE_BRACKET_NAVIGATION_WIDGET_H #endif // COCKATRICE_EDHREC_COMMANDER_API_RESPONSE_BRACKET_NAVIGATION_WIDGET_H

View file

@ -1,6 +1,9 @@
#include "edhrec_commander_api_response_budget_navigation_widget.h" #include "edhrec_commander_api_response_budget_navigation_widget.h"
#include "../../interface/theme_manager.h"
#include <QChar> #include <QChar>
#include <QFont>
#include <QGridLayout> #include <QGridLayout>
#include <QLabel> #include <QLabel>
#include <QPushButton> #include <QPushButton>
@ -25,7 +28,7 @@ EdhrecCommanderApiResponseBudgetNavigationWidget::EdhrecCommanderApiResponseBudg
QString label = option.isEmpty() ? "Any" : option.at(0).toUpper() + option.mid(1); QString label = option.isEmpty() ? "Any" : option.at(0).toUpper() + option.mid(1);
QPushButton *btn = new QPushButton(label, this); QPushButton *btn = new QPushButton(label, this);
btn->setMinimumHeight(84); btn->setMinimumHeight(84);
btn->setStyleSheet("font-size: 24px"); btn->setStyleSheet(QString());
budgetButtons[option] = btn; budgetButtons[option] = btn;
layout->addWidget(btn, 4, i); layout->addWidget(btn, 4, i);
connect(btn, &QPushButton::clicked, this, [=, this]() { connect(btn, &QPushButton::clicked, this, [=, this]() {
@ -35,6 +38,10 @@ EdhrecCommanderApiResponseBudgetNavigationWidget::EdhrecCommanderApiResponseBudg
}); });
} }
applyInterfaceDefaultFont();
connect(themeManager, &ThemeManager::fontsChanged, this,
&EdhrecCommanderApiResponseBudgetNavigationWidget::applyInterfaceDefaultFont);
updateOptionButtonSelection(budgetButtons, ""); updateOptionButtonSelection(budgetButtons, "");
retranslateUi(); retranslateUi();
@ -96,8 +103,18 @@ void EdhrecCommanderApiResponseBudgetNavigationWidget::updateOptionButtonSelecti
const QString &selectedKey) const QString &selectedKey)
{ {
for (auto it = buttons.begin(); it != buttons.end(); ++it) { for (auto it = buttons.begin(); it != buttons.end(); ++it) {
it.value()->setStyleSheet(it.key() == selectedKey const bool selected = (it.key() == selectedKey);
? "background-color: lightblue; font-weight: bold; font-size: 24px;" it.value()->setStyleSheet(selected ? QStringLiteral("background-color: lightblue;") : QString());
: "font-size: 24px"); QFont buttonFont = themeManager->appFont(AppFont::InterfaceDefault);
buttonFont.setPixelSize(24);
buttonFont.setWeight(selected ? QFont::Bold : QFont::Normal);
it.value()->setFont(buttonFont);
} }
}
void EdhrecCommanderApiResponseBudgetNavigationWidget::applyInterfaceDefaultFont()
{
// Family comes from the InterfaceDefault theme role; the 24px size stays
// caller-side and the selected button keeps its bold emphasis.
updateOptionButtonSelection(budgetButtons, selectedBudget);
} }

View file

@ -37,6 +37,9 @@ private:
QMap<QString, QPushButton *> budgetButtons; QMap<QString, QPushButton *> budgetButtons;
void updateOptionButtonSelection(QMap<QString, QPushButton *> &buttons, const QString &selectedKey); void updateOptionButtonSelection(QMap<QString, QPushButton *> &buttons, const QString &selectedKey);
private slots:
void applyInterfaceDefaultFont();
}; };
#endif // COCKATRICE_EDHREC_COMMANDER_API_RESPONSE_BUDGET_NAVIGATION_WIDGET_H #endif // COCKATRICE_EDHREC_COMMANDER_API_RESPONSE_BUDGET_NAVIGATION_WIDGET_H

View file

@ -1,9 +1,11 @@
#include "edhrec_commander_api_response_navigation_widget.h" #include "edhrec_commander_api_response_navigation_widget.h"
#include "../../interface/theme_manager.h"
#include "../../tab_edhrec_main.h" #include "../../tab_edhrec_main.h"
#include "edhrec_commander_api_response_bracket_navigation_widget.h" #include "edhrec_commander_api_response_bracket_navigation_widget.h"
#include "edhrec_commander_api_response_budget_navigation_widget.h" #include "edhrec_commander_api_response_budget_navigation_widget.h"
#include <QFont>
#include <QGridLayout> #include <QGridLayout>
#include <QObject> #include <QObject>
#include <QPushButton> #include <QPushButton>
@ -30,10 +32,14 @@ EdhrecCommanderApiResponseNavigationWidget::EdhrecCommanderApiResponseNavigation
comboPushButton = new QPushButton(this); comboPushButton = new QPushButton(this);
comboPushButton->setMinimumHeight(84); comboPushButton->setMinimumHeight(84);
comboPushButton->setStyleSheet("font-size: 24px"); comboPushButton->setStyleSheet(QString());
averageDeckPushButton = new QPushButton(this); averageDeckPushButton = new QPushButton(this);
averageDeckPushButton->setMinimumHeight(84); averageDeckPushButton->setMinimumHeight(84);
averageDeckPushButton->setStyleSheet("font-size: 24px"); averageDeckPushButton->setStyleSheet(QString());
applyInterfaceDefaultFont();
connect(themeManager, &ThemeManager::fontsChanged, this,
&EdhrecCommanderApiResponseNavigationWidget::applyInterfaceDefaultFont);
layout->addWidget(comboPushButton, 0, 0, 1, 1); layout->addWidget(comboPushButton, 0, 0, 1, 1);
layout->addWidget(averageDeckPushButton, 0, 1, 1, 1); layout->addWidget(averageDeckPushButton, 0, 1, 1, 1);
@ -61,6 +67,15 @@ void EdhrecCommanderApiResponseNavigationWidget::retranslateUi()
averageDeckPushButton->setText(tr("Average Deck")); averageDeckPushButton->setText(tr("Average Deck"));
} }
void EdhrecCommanderApiResponseNavigationWidget::applyInterfaceDefaultFont()
{
// Family comes from the InterfaceDefault theme role; the 24px size stays caller-side.
QFont navFont = themeManager->appFont(AppFont::InterfaceDefault);
navFont.setPixelSize(24);
comboPushButton->setFont(navFont);
averageDeckPushButton->setFont(navFont);
}
QString EdhrecCommanderApiResponseNavigationWidget::addNavigationOptionsToUrl(QString baseUrl) QString EdhrecCommanderApiResponseNavigationWidget::addNavigationOptionsToUrl(QString baseUrl)
{ {
if (!bracketNavigationWidget->getSelectedGameChanger().isEmpty()) { if (!bracketNavigationWidget->getSelectedGameChanger().isEmpty()) {

View file

@ -35,6 +35,9 @@ public slots:
signals: signals:
void requestUrl(QString url); void requestUrl(QString url);
private slots:
void applyInterfaceDefaultFont();
private: private:
QGridLayout *layout; QGridLayout *layout;

View file

@ -6,6 +6,7 @@
#include "tab_developer.h" #include "tab_developer.h"
#include "../interface/theme_manager.h"
#include "libcockatrice/protocol/pb/response.pb.h" #include "libcockatrice/protocol/pb/response.pb.h"
#include <QAbstractItemView> #include <QAbstractItemView>
@ -100,6 +101,20 @@ TabDeveloper::TabDeveloper(TabSupervisor *_tabSupervisor, AbstractClient *_clien
central->setLayout(mainLayout); central->setLayout(mainLayout);
setCentralWidget(central); setCentralWidget(central);
// Separator rows store their font on the item; re-apply the role family on
// font changes (value cells keep the default font and need only a repaint).
connect(themeManager, &ThemeManager::fontsChanged, this, [this] {
QFont separatorFont = themeManager->appFont(AppFont::InterfaceDefault);
separatorFont.setBold(true);
for (int row = 0; row < statsTable->rowCount(); ++row) {
auto *labelItem = statsTable->item(row, 0);
if (labelItem && !(labelItem->flags() & Qt::ItemIsSelectable)) {
labelItem->setFont(separatorFont);
}
}
statsTable->viewport()->update();
});
retranslateUi(); retranslateUi();
} }
@ -155,7 +170,8 @@ void TabDeveloper::appendSeparatorRow(const QString &sectionTitle)
const int row = statsTable->rowCount(); const int row = statsTable->rowCount();
statsTable->insertRow(row); statsTable->insertRow(row);
auto *labelItem = new QTableWidgetItem(sectionTitle); auto *labelItem = new QTableWidgetItem(sectionTitle);
auto font = labelItem->font(); // Family comes from the InterfaceDefault theme role; bold stays caller-side.
QFont font = themeManager->appFont(AppFont::InterfaceDefault);
font.setBold(true); font.setBold(true);
labelItem->setFont(font); labelItem->setFont(font);
labelItem->setFlags(labelItem->flags() & ~Qt::ItemIsSelectable); labelItem->setFlags(labelItem->flags() & ~Qt::ItemIsSelectable);

View file

@ -4,6 +4,7 @@
#include "../../deck_loader/deck_loader.h" #include "../../deck_loader/deck_loader.h"
#include "../cards/card_size_widget.h" #include "../cards/card_size_widget.h"
#include "../general/layout_containers/flow_widget.h" #include "../general/layout_containers/flow_widget.h"
#include "../interface/theme_manager.h"
#include "../visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.h" #include "../visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.h"
#include "../visual_deck_storage/deck_preview/public_deck_preview_widget.h" #include "../visual_deck_storage/deck_preview/public_deck_preview_widget.h"
#include "../visual_deck_storage/remote_public_decks_model.h" #include "../visual_deck_storage/remote_public_decks_model.h"
@ -45,7 +46,17 @@ TabPublicDecks::TabPublicDecks(TabSupervisor *_tabSupervisor, AbstractClient *_c
cardSize = SettingsCache::instance().cardsDisplay().getVisualDeckStorageCardSize(); cardSize = SettingsCache::instance().cardsDisplay().getVisualDeckStorageCardSize();
titleLabel = new QLabel(tr("Public decks of %1").arg(userName.toHtmlEscaped()), this); titleLabel = new QLabel(tr("Public decks of %1").arg(userName.toHtmlEscaped()), this);
titleLabel->setFont(QtUtils::boldFont(titleLabel->font())); // Family comes from the InterfaceDefault theme role; bold stays caller-side.
auto applyTitleFont = [this]() {
titleLabel->setFont(QtUtils::boldFont(themeManager->appFont(AppFont::InterfaceDefault)));
};
applyTitleFont();
connect(themeManager, &ThemeManager::fontsChanged, this, [this, applyTitleFont] {
applyTitleFont();
// Preview cells paint with the live role font; repaint the grid so the
// new family shows without waiting for a scroll or hover.
flowWidget->update();
});
auto *headerLayout = new QHBoxLayout; auto *headerLayout = new QHBoxLayout;
headerLayout->addWidget(titleLabel); headerLayout->addWidget(titleLabel);

View file

@ -1,5 +1,6 @@
#include "tab_report.h" #include "tab_report.h"
#include "../interface/theme_manager.h"
#include "../server/user/user_context_menu.h" #include "../server/user/user_context_menu.h"
#include "../utility/report_utils.h" #include "../utility/report_utils.h"
#include "abstract_client.h" #include "abstract_client.h"
@ -12,6 +13,7 @@
#include <QComboBox> #include <QComboBox>
#include <QDateTime> #include <QDateTime>
#include <QDebug> #include <QDebug>
#include <QFont>
#include <QGridLayout> #include <QGridLayout>
#include <QGroupBox> #include <QGroupBox>
#include <QHBoxLayout> #include <QHBoxLayout>
@ -137,7 +139,8 @@ TabReport::TabReport(TabSupervisor *_tabSupervisor, AbstractClient *_client) : T
chatGroup = new QGroupBox; chatGroup = new QGroupBox;
chatLogEdit = new QTextEdit; chatLogEdit = new QTextEdit;
chatLogEdit->setReadOnly(true); chatLogEdit->setReadOnly(true);
chatLogEdit->setFont(QtUtils::monospaceFont()); applyChatLogFont();
connect(themeManager, &ThemeManager::fontsChanged, this, &TabReport::applyChatLogFont);
auto *chatLayout = new QVBoxLayout(chatGroup); auto *chatLayout = new QVBoxLayout(chatGroup);
chatLayout->setContentsMargins(4, 4, 4, 4); chatLayout->setContentsMargins(4, 4, 4, 4);
chatLayout->addWidget(chatLogEdit); chatLayout->addWidget(chatLogEdit);
@ -269,6 +272,11 @@ TabReport::TabReport(TabSupervisor *_tabSupervisor, AbstractClient *_client) : T
refreshList(); refreshList();
} }
void TabReport::applyChatLogFont()
{
chatLogEdit->setFont(report_utils::reportLogFont());
}
void TabReport::retranslateUi() void TabReport::retranslateUi()
{ {
userContextMenu->retranslateUi(); userContextMenu->retranslateUi();

View file

@ -62,6 +62,7 @@ private slots:
void statsResponse(const Response &response); void statsResponse(const Response &response);
void reopenReport(); void reopenReport();
void reopenResponse(const Response &response); void reopenResponse(const Response &response);
void applyChatLogFont();
private: private:
int selectedReportId() const; int selectedReportId() const;

View file

@ -20,7 +20,6 @@
#include "tab_deck_editor_visual_tab_widget.h" #include "tab_deck_editor_visual_tab_widget.h"
#include <QAction> #include <QAction>
#include <QApplication>
#include <QByteArray> #include <QByteArray>
#include <QDebug> #include <QDebug>
#include <QDockWidget> #include <QDockWidget>
@ -261,17 +260,25 @@ void TabDeckEditorVisual::processMainboardCardClick(const QMouseEvent *event,
} }
} }
/** @brief Get a zone name corresponding to keyboard modifiers. */
static QString zoneForClickModifiers(const Qt::KeyboardModifiers &modifiers)
{
if (modifiers.testFlag(Qt::ControlModifier)) {
return DECK_ZONE_SIDE;
} else if (modifiers.testFlag(Qt::ShiftModifier)) {
return DECK_ZONE_MAYBEBOARD;
} else {
return DECK_ZONE_MAIN;
}
}
/** @brief Handles clicks on cards in the database display. */ /** @brief Handles clicks on cards in the database display. */
void TabDeckEditorVisual::processDatabaseCardClick(const QMouseEvent *event, const ExactCard &card) void TabDeckEditorVisual::processDatabaseCardClick(const QMouseEvent *event, const ExactCard &card)
{ {
if (event->button() == Qt::LeftButton) { if (event->button() == Qt::LeftButton) {
if (QApplication::keyboardModifiers() & Qt::ControlModifier) { addCard(card, zoneForClickModifiers(event->modifiers()));
addCard(card, DECK_ZONE_SIDE);
} else {
addCard(card, DECK_ZONE_MAIN);
}
} else if (event->button() == Qt::RightButton) { } else if (event->button() == Qt::RightButton) {
decrementCard(card, DECK_ZONE_MAIN); decrementCard(card, zoneForClickModifiers(event->modifiers()));
} else if (event->button() == Qt::MiddleButton) { } else if (event->button() == Qt::MiddleButton) {
deckDockWidget->actRemoveCard(); deckDockWidget->actRemoveCard();
} }

View file

@ -1,5 +1,6 @@
#include "card_completer_delegate.h" #include "card_completer_delegate.h"
#include "../../interface/theme_manager.h"
#include "../../pixel_map_generator.h" #include "../../pixel_map_generator.h"
#include "../cards/additional_info/mana_cost_widget.h" #include "../cards/additional_info/mana_cost_widget.h"
#include "libcockatrice/card/database/card_database_querier.h" #include "libcockatrice/card/database/card_database_querier.h"
@ -182,7 +183,7 @@ void CardCompleterDelegate::drawManaSymbol(QPainter *p, QPoint centre, const QSt
p->setBrush(Qt::NoBrush); p->setBrush(Qt::NoBrush);
p->drawEllipse(pip.adjusted(1, 1, -1, -1)); p->drawEllipse(pip.adjusted(1, 1, -1, -1));
QFont f = QtUtils::boldFont(p->font()); QFont f = QtUtils::boldFont(themeManager->appFont(AppFont::CardText));
f.setPixelSize(qMax(radius - 1, 7)); f.setPixelSize(qMax(radius - 1, 7));
p->setFont(f); p->setFont(f);
@ -429,7 +430,7 @@ void CardCompleterDelegate::paint(QPainter *painter, const QStyleOptionViewItem
const QRect nameRect = showInfoBand ? QRect(textLeft, rect.top() + 2, textWidth, 20) const QRect nameRect = showInfoBand ? QRect(textLeft, rect.top() + 2, textWidth, 20)
: QRect(textLeft, rect.top() + 2, textWidth, rect.height() - 4); : QRect(textLeft, rect.top() + 2, textWidth, rect.height() - 4);
QFont f = option.font; QFont f = themeManager->appFont(AppFont::CardText);
f.setPixelSize(13); f.setPixelSize(13);
f.setBold(true); f.setBold(true);
@ -451,7 +452,7 @@ void CardCompleterDelegate::paint(QPainter *painter, const QStyleOptionViewItem
if (showInfoBand) { if (showInfoBand) {
const QRect infoRect(textLeft, rect.top() + 22, textWidth, rect.height() - 24); const QRect infoRect(textLeft, rect.top() + 22, textWidth, rect.height() - 24);
QFont f = option.font; QFont f = themeManager->appFont(AppFont::CardText);
f.setPixelSize(10); f.setPixelSize(10);
painter->setFont(f); painter->setFont(f);

View file

@ -1,5 +1,6 @@
#include "card_completer_styler.h" #include "card_completer_styler.h"
#include "../../interface/theme_manager.h"
#include "../cards/card_info_picture_enlarged_widget.h" #include "../cards/card_info_picture_enlarged_widget.h"
#include "card_completer_delegate.h" #include "card_completer_delegate.h"
#include "libcockatrice/card/database/card_database_querier.h" #include "libcockatrice/card/database/card_database_querier.h"
@ -73,6 +74,10 @@ CardCompleterStyler::CardCompleterStyler(QCompleter *completer, QObject *parent)
popup->viewport()->setMouseTracking(true); popup->viewport()->setMouseTracking(true);
// The delegate reads the CardText role at paint time, so a font change
// only needs a repaint of the popup.
connect(themeManager, &ThemeManager::fontsChanged, popup, [popup] { popup->viewport()->update(); });
popup->installEventFilter(this); popup->installEventFilter(this);
popup->viewport()->installEventFilter(this); popup->viewport()->installEventFilter(this);

View file

@ -1,5 +1,6 @@
#include "report_utils.h" #include "report_utils.h"
#include "../../theme_manager.h"
#include "libcockatrice/protocol/pb/serverinfo_report.pb.h" #include "libcockatrice/protocol/pb/serverinfo_report.pb.h"
#include <QApplication> #include <QApplication>
@ -7,6 +8,7 @@
#include <QChar> #include <QChar>
#include <QColor> #include <QColor>
#include <QDateTime> #include <QDateTime>
#include <QFontDatabase>
#include <QPalette> #include <QPalette>
#include <QSharedPointer> #include <QSharedPointer>
#include <QTableWidget> #include <QTableWidget>
@ -19,6 +21,20 @@
namespace report_utils namespace report_utils
{ {
QFont reportLogFont()
{
if (themeManager->hasAppFont(AppFont::ChatLog)) {
return themeManager->appFont(AppFont::ChatLog);
}
QFont monoFont(QStringLiteral("monospace"));
monoFont.setStyleHint(QFont::Monospace);
const int systemPointSize = QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize();
if (systemPointSize > 0) {
monoFont.setPointSize(systemPointSize);
}
return monoFont;
}
namespace namespace
{ {
QColor reportStatusColor(const QString &status) QColor reportStatusColor(const QString &status)

View file

@ -1,6 +1,7 @@
#ifndef REPORT_UTILS_H #ifndef REPORT_UTILS_H
#define REPORT_UTILS_H #define REPORT_UTILS_H
#include <QFont>
#include <QString> #include <QString>
#include <QtGlobal> #include <QtGlobal>
@ -11,6 +12,12 @@ class ServerInfo_Report;
namespace report_utils namespace report_utils
{ {
/** @brief Font for the report chat-log views: the theme's ChatLog role when it
* sets one, otherwise the monospace font these views used before the
* role existed. Shared by the three report surfaces so they cannot
* drift apart. */
QFont reportLogFont();
QString formatReportCategory(const QString &category); QString formatReportCategory(const QString &category);
QString formatReportTime(qint64 secondsSinceEpoch); QString formatReportTime(qint64 secondsSinceEpoch);

View file

@ -1,5 +1,8 @@
#include "visual_deck_editor_placeholder_widget.h" #include "visual_deck_editor_placeholder_widget.h"
#include "../interface/theme_manager.h"
#include <QFont>
#include <QLabel> #include <QLabel>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <qnamespace.h> #include <qnamespace.h>
@ -28,10 +31,12 @@ VisualDeckEditorPlaceholderWidget::VisualDeckEditorPlaceholderWidget(QWidget *pa
textLabel->setStyleSheet(R"( textLabel->setStyleSheet(R"(
QLabel { QLabel {
color: palette(mid); color: palette(mid);
font-size: 14px;
padding: 10px; padding: 10px;
} }
)"); )");
applyInterfaceDefaultFont();
connect(themeManager, &ThemeManager::fontsChanged, this,
&VisualDeckEditorPlaceholderWidget::applyInterfaceDefaultFont);
mainLayout->addWidget(imageLabel); mainLayout->addWidget(imageLabel);
mainLayout->addWidget(textLabel); mainLayout->addWidget(textLabel);
@ -42,4 +47,12 @@ VisualDeckEditorPlaceholderWidget::VisualDeckEditorPlaceholderWidget(QWidget *pa
void VisualDeckEditorPlaceholderWidget::retranslateUi() void VisualDeckEditorPlaceholderWidget::retranslateUi()
{ {
textLabel->setText(tr("Add cards using the search bar or database tab to have them appear here")); textLabel->setText(tr("Add cards using the search bar or database tab to have them appear here"));
}
void VisualDeckEditorPlaceholderWidget::applyInterfaceDefaultFont()
{
// Family comes from the InterfaceDefault theme role; the 14px size stays caller-side.
QFont placeholderFont = themeManager->appFont(AppFont::InterfaceDefault);
placeholderFont.setPixelSize(14);
textLabel->setFont(placeholderFont);
} }

View file

@ -15,6 +15,9 @@ public:
explicit VisualDeckEditorPlaceholderWidget(QWidget *parent = nullptr); explicit VisualDeckEditorPlaceholderWidget(QWidget *parent = nullptr);
void retranslateUi(); void retranslateUi();
private slots:
void applyInterfaceDefaultFont();
private: private:
QVBoxLayout *mainLayout; QVBoxLayout *mainLayout;
QLabel *imageLabel; QLabel *imageLabel;

View file

@ -1,5 +1,7 @@
#include "deck_preview_tag_addition_widget.h" #include "deck_preview_tag_addition_widget.h"
#include "../../interface/theme_manager.h"
#include <QBrush> #include <QBrush>
#include <QColor> #include <QColor>
#include <QFlags> #include <QFlags>
@ -26,6 +28,10 @@ DeckPreviewTagAdditionWidget::DeckPreviewTagAdditionWidget(QWidget *_parent, QSt
// Adjust widget size // Adjust widget size
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
// The tag text reads the InterfaceDefault role at paint time, so a font
// change only needs a repaint.
connect(themeManager, &ThemeManager::fontsChanged, this, [this] { update(); });
} }
QSize DeckPreviewTagAdditionWidget::sizeHint() const QSize DeckPreviewTagAdditionWidget::sizeHint() const
@ -65,8 +71,9 @@ void DeckPreviewTagAdditionWidget::paintEvent(QPaintEvent *event)
painter.setPen(borderPen); painter.setPen(borderPen);
painter.drawRect(rect().adjusted(0, 0, -1, -1)); // Adjust for pen width painter.drawRect(rect().adjusted(0, 0, -1, -1)); // Adjust for pen width
// Calculate font size based on widget height // Calculate font size based on widget height. Family comes from the
QFont font = painter.font(); // InterfaceDefault theme role; the height-derived size stays as before.
QFont font = themeManager->appFont(AppFont::InterfaceDefault);
int fontSize = std::max(10, height() / 2); // Ensure a minimum font size of 10 int fontSize = std::max(10, height() / 2); // Ensure a minimum font size of 10
font.setPointSize(fontSize); font.setPointSize(fontSize);
painter.setFont(font); painter.setFont(font);

View file

@ -1,5 +1,7 @@
#include "deck_preview_tag_display_widget.h" #include "deck_preview_tag_display_widget.h"
#include "../../interface/theme_manager.h"
#include <QBrush> #include <QBrush>
#include <QColor> #include <QColor>
#include <QFlags> #include <QFlags>
@ -41,6 +43,10 @@ DeckPreviewTagDisplayWidget::DeckPreviewTagDisplayWidget(QWidget *parent, const
// Adjust widget size // Adjust widget size
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
// The tag text reads the InterfaceDefault role at paint time, so a font
// change only needs a repaint.
connect(themeManager, &ThemeManager::fontsChanged, this, [this] { update(); });
// Connect close button to the remove signal // Connect close button to the remove signal
// connect(closeButton, &QPushButton::clicked, this, &DeckPreviewTagDisplayWidget::tagClosed); // connect(closeButton, &QPushButton::clicked, this, &DeckPreviewTagDisplayWidget::tagClosed);
} }
@ -117,8 +123,9 @@ void DeckPreviewTagDisplayWidget::paintEvent(QPaintEvent *event)
painter.setPen(borderPen); painter.setPen(borderPen);
painter.drawRect(rect().adjusted(0, 0, -1, -1)); painter.drawRect(rect().adjusted(0, 0, -1, -1));
// Calculate font size based on widget height // Calculate font size based on widget height. Family comes from the
QFont font = painter.font(); // InterfaceDefault theme role; the height-derived size stays as before.
QFont font = themeManager->appFont(AppFont::InterfaceDefault);
int fontSize = std::max(10, height() / 2); // Ensure a minimum font size of 10 int fontSize = std::max(10, height() / 2); // Ensure a minimum font size of 10
font.setPointSize(fontSize); font.setPointSize(fontSize);
painter.setFont(font); painter.setFont(font);

View file

@ -1,6 +1,7 @@
@page editing_decks Editing Decks @page editing_decks Editing Decks
- @subpage editing_decks_classic - @subpage editing_decks_classic
- @subpage editing_decks_visual - @subpage editing_decks_visual
- @subpage editing_decks_printings - @subpage visual_database_display
- @subpage editing_decks_printings

View file

@ -70,4 +70,4 @@ confirm.
The action will automatically import the deck from the online service without any other required user action. The action will automatically import the deck from the online service without any other required user action.
Currently supported services are Archidekt, Deckstats, Moxfield, and TappedOut. Currently supported services are Archidekt, Deckstats, ManaBox, Moxfield, and TappedOut.

Some files were not shown because too many files have changed in this diff Show more