From 86b5003f07e3a11fd84f90a91a36dd70bc51441f Mon Sep 17 00:00:00 2001 From: luisangelsm Date: Fri, 6 Mar 2026 12:29:06 +0100 Subject: [PATCH] Use target app and version in themes meta --- YACReader/main.cpp | 2 +- YACReader/themes/builtin_classic.json | 4 ++- YACReader/themes/builtin_dark.json | 4 ++- YACReader/themes/builtin_light.json | 4 ++- YACReader/themes/theme_factory.cpp | 2 ++ YACReaderLibrary/main.cpp | 2 +- YACReaderLibrary/themes/builtin_classic.json | 4 ++- YACReaderLibrary/themes/builtin_dark.json | 4 ++- YACReaderLibrary/themes/builtin_light.json | 4 ++- YACReaderLibrary/themes/theme_factory.cpp | 2 ++ common/themes/appearance_tab_widget.cpp | 9 ++++-- common/themes/theme_meta.h | 2 ++ common/themes/theme_repository.cpp | 31 ++++++++++++++++---- common/themes/theme_repository.h | 5 ++-- 14 files changed, 60 insertions(+), 19 deletions(-) diff --git a/YACReader/main.cpp b/YACReader/main.cpp index d1c98e22..66b0d52a 100644 --- a/YACReader/main.cpp +++ b/YACReader/main.cpp @@ -120,7 +120,7 @@ int main(int argc, char *argv[]) auto *appearanceConfig = new AppearanceConfiguration( YACReader::getSettingsPath() + "/YACReader.ini", qApp); auto *themeRepo = new ThemeRepository( - ":/themes", YACReader::getSettingsPath() + "/themes/user"); + ":/themes", YACReader::getSettingsPath() + "/themes/user", "YACReader"); ThemeManager::instance().initialize(appearanceConfig, themeRepo); if (QIcon::hasThemeIcon("YACReader")) { diff --git a/YACReader/themes/builtin_classic.json b/YACReader/themes/builtin_classic.json index bf1b3415..fcac45c7 100644 --- a/YACReader/themes/builtin_classic.json +++ b/YACReader/themes/builtin_classic.json @@ -19,7 +19,9 @@ "meta": { "displayName": "Default Classic", "id": "builtin/classic", - "variant": "dark" + "targetApp": "YACReader", + "variant": "dark", + "version": "10.0.0" }, "shortcutsIcons": { "iconColor": "#404040" diff --git a/YACReader/themes/builtin_dark.json b/YACReader/themes/builtin_dark.json index 193fa0b6..7efd26ae 100644 --- a/YACReader/themes/builtin_dark.json +++ b/YACReader/themes/builtin_dark.json @@ -19,7 +19,9 @@ "meta": { "displayName": "Default Dark", "id": "builtin/dark", - "variant": "dark" + "targetApp": "YACReader", + "variant": "dark", + "version": "10.0.0" }, "shortcutsIcons": { "iconColor": "#d0d0d0" diff --git a/YACReader/themes/builtin_light.json b/YACReader/themes/builtin_light.json index a7aadc67..dc9209f7 100644 --- a/YACReader/themes/builtin_light.json +++ b/YACReader/themes/builtin_light.json @@ -19,7 +19,9 @@ "meta": { "displayName": "Default Light", "id": "builtin/light", - "variant": "light" + "targetApp": "YACReader", + "variant": "light", + "version": "10.0.0" }, "shortcutsIcons": { "iconColor": "#606060" diff --git a/YACReader/themes/theme_factory.cpp b/YACReader/themes/theme_factory.cpp index 7b7d052a..6f5cfa8e 100644 --- a/YACReader/themes/theme_factory.cpp +++ b/YACReader/themes/theme_factory.cpp @@ -363,6 +363,8 @@ Theme makeTheme(const QJsonObject &json) p.meta.variant = ThemeVariant::Light; else if (variantStr == "dark") p.meta.variant = ThemeVariant::Dark; + p.meta.targetApp = o["targetApp"].toString(p.meta.targetApp); + p.meta.version = o["version"].toString(p.meta.version); } Theme theme = makeTheme(p); diff --git a/YACReaderLibrary/main.cpp b/YACReaderLibrary/main.cpp index 4d82ea86..9c339e8f 100644 --- a/YACReaderLibrary/main.cpp +++ b/YACReaderLibrary/main.cpp @@ -145,7 +145,7 @@ int main(int argc, char **argv) auto *appearanceConfig = new AppearanceConfiguration( YACReader::getSettingsPath() + "/YACReaderLibrary.ini", qApp); auto *themeRepo = new ThemeRepository( - ":/themes", YACReader::getSettingsPath() + "/themes/user"); + ":/themes", YACReader::getSettingsPath() + "/themes/user", "YACReaderLibrary"); ThemeManager::instance().initialize(appearanceConfig, themeRepo); // Set window icon according to Freedesktop icon specification diff --git a/YACReaderLibrary/themes/builtin_classic.json b/YACReaderLibrary/themes/builtin_classic.json index ea571a11..5fb6e477 100644 --- a/YACReaderLibrary/themes/builtin_classic.json +++ b/YACReaderLibrary/themes/builtin_classic.json @@ -106,7 +106,9 @@ "meta": { "displayName": "Default Classic", "id": "builtin/classic", - "variant": "dark" + "targetApp": "YACReaderLibrary", + "variant": "dark", + "version": "10.0.0" }, "metadataScraperDialog": { "busyIndicatorColor": "#ffffff", diff --git a/YACReaderLibrary/themes/builtin_dark.json b/YACReaderLibrary/themes/builtin_dark.json index 6a9a7a28..7d043c8f 100644 --- a/YACReaderLibrary/themes/builtin_dark.json +++ b/YACReaderLibrary/themes/builtin_dark.json @@ -106,7 +106,9 @@ "meta": { "displayName": "Default Dark", "id": "builtin/dark", - "variant": "dark" + "targetApp": "YACReaderLibrary", + "variant": "dark", + "version": "10.0.0" }, "metadataScraperDialog": { "busyIndicatorColor": "#ffffff", diff --git a/YACReaderLibrary/themes/builtin_light.json b/YACReaderLibrary/themes/builtin_light.json index 9d514d4b..3abc1dd7 100644 --- a/YACReaderLibrary/themes/builtin_light.json +++ b/YACReaderLibrary/themes/builtin_light.json @@ -106,7 +106,9 @@ "meta": { "displayName": "Default Light", "id": "builtin/light", - "variant": "light" + "targetApp": "YACReaderLibrary", + "variant": "light", + "version": "10.0.0" }, "metadataScraperDialog": { "busyIndicatorColor": "#000000", diff --git a/YACReaderLibrary/themes/theme_factory.cpp b/YACReaderLibrary/themes/theme_factory.cpp index eaba930d..bf1afbcd 100644 --- a/YACReaderLibrary/themes/theme_factory.cpp +++ b/YACReaderLibrary/themes/theme_factory.cpp @@ -1165,6 +1165,8 @@ Theme makeTheme(const QJsonObject &json) p.meta.variant = ThemeVariant::Light; else if (variantStr == "dark") p.meta.variant = ThemeVariant::Dark; + p.meta.targetApp = o["targetApp"].toString(p.meta.targetApp); + p.meta.version = o["version"].toString(p.meta.version); } Theme theme = makeTheme(p); diff --git a/common/themes/appearance_tab_widget.cpp b/common/themes/appearance_tab_widget.cpp index 9dcb49c2..bcf1f218 100644 --- a/common/themes/appearance_tab_widget.cpp +++ b/common/themes/appearance_tab_widget.cpp @@ -264,10 +264,13 @@ void AppearanceTabWidget::importTheme() if (path.isEmpty() || !repository) return; - const QString id = repository->importThemeFromFile(path); + QString errorMessage; + const QString id = repository->importThemeFromFile(path, &errorMessage); if (id.isEmpty()) { - QMessageBox::warning(this, tr("Import failed"), - tr("Could not import theme from:\n%1").arg(path)); + const QString detail = errorMessage.isEmpty() + ? tr("Could not import theme from:\n%1").arg(path) + : tr("Could not import theme from:\n%1\n\n%2").arg(path, errorMessage); + QMessageBox::warning(this, tr("Import failed"), detail); return; } diff --git a/common/themes/theme_meta.h b/common/themes/theme_meta.h index e3b9c6d8..f4f803b3 100644 --- a/common/themes/theme_meta.h +++ b/common/themes/theme_meta.h @@ -9,6 +9,8 @@ struct ThemeMeta { QString id; QString displayName; ThemeVariant variant; + QString targetApp; + QString version; }; #endif // THEME_META_H diff --git a/common/themes/theme_repository.cpp b/common/themes/theme_repository.cpp index d76695c8..96a3e0d5 100644 --- a/common/themes/theme_repository.cpp +++ b/common/themes/theme_repository.cpp @@ -6,8 +6,8 @@ #include #include -ThemeRepository::ThemeRepository(const QString &qrcPrefix, const QString &userThemesDir) - : qrcPrefix(qrcPrefix), userThemesDir(userThemesDir) +ThemeRepository::ThemeRepository(const QString &qrcPrefix, const QString &userThemesDir, const QString &targetApp) + : qrcPrefix(qrcPrefix), userThemesDir(userThemesDir), targetApp(targetApp) { scanBuiltins(); scanUserThemes(); @@ -64,9 +64,14 @@ QString ThemeRepository::saveUserTheme(QJsonObject themeJson) const QString uuid = QUuid::createUuid().toString(QUuid::WithoutBraces); id = "user/" + uuid; metaObj["id"] = id; - themeJson["meta"] = metaObj; } + // Always stamp targetApp so saved themes are always identifiable + if (metaObj["targetApp"].toString().isEmpty()) + metaObj["targetApp"] = targetApp; + + themeJson["meta"] = metaObj; + // Extract uuid from "user/" const QString uuid = id.mid(5); // skip "user/" const QString filePath = filePathForUserTheme(uuid); @@ -100,11 +105,23 @@ bool ThemeRepository::deleteUserTheme(const QString &themeId) return false; } -QString ThemeRepository::importThemeFromFile(const QString &filePath) +QString ThemeRepository::importThemeFromFile(const QString &filePath, QString *errorMessage) { QJsonObject json = readJsonFile(filePath); - if (json.isEmpty()) + if (json.isEmpty()) { + if (errorMessage) + *errorMessage = QObject::tr("The file could not be read or is not valid JSON."); return { }; + } + + // Check that the theme targets the correct application + const auto metaIn = json["meta"].toObject(); + const QString themeTargetApp = metaIn["targetApp"].toString(); + if (!themeTargetApp.isEmpty() && themeTargetApp != targetApp) { + if (errorMessage) + *errorMessage = QObject::tr("This theme is for %1, not %2.").arg(themeTargetApp, targetApp); + return { }; + } // Force a new user id regardless of what the file contains auto metaObj = json["meta"].toObject(); @@ -181,7 +198,9 @@ ThemeMeta ThemeRepository::extractMeta(const QJsonObject &json) return ThemeMeta { meta["id"].toString(), meta["displayName"].toString(), - (meta["variant"].toString() == "light") ? ThemeVariant::Light : ThemeVariant::Dark + (meta["variant"].toString() == "light") ? ThemeVariant::Light : ThemeVariant::Dark, + meta["targetApp"].toString(), + meta["version"].toString() }; } diff --git a/common/themes/theme_repository.h b/common/themes/theme_repository.h index e9ec4a85..e1eeaebd 100644 --- a/common/themes/theme_repository.h +++ b/common/themes/theme_repository.h @@ -17,7 +17,7 @@ struct ThemeListEntry { class ThemeRepository { public: - explicit ThemeRepository(const QString &qrcPrefix, const QString &userThemesDir); + explicit ThemeRepository(const QString &qrcPrefix, const QString &userThemesDir, const QString &targetApp); QList availableThemes() const; bool contains(const QString &themeId) const; @@ -25,13 +25,14 @@ public: QString saveUserTheme(QJsonObject themeJson); bool deleteUserTheme(const QString &themeId); - QString importThemeFromFile(const QString &filePath); + QString importThemeFromFile(const QString &filePath, QString *errorMessage = nullptr); void refresh(); private: QString qrcPrefix; QString userThemesDir; + QString targetApp; struct BuiltinEntry { QString id;