diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 4263fc6e2..63ccc4e9c 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -403,7 +403,6 @@ set(cockatrice_SOURCES src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_widget.cpp src/interface/widgets/tabs/api/commander_spellbook/handle_commander_brackets.cpp src/interface/widgets/onboarding/banner_shader_config.h - src/interface/widgets/onboarding/brand_colors.h src/interface/widgets/onboarding/first_run_wizard.cpp src/interface/widgets/onboarding/first_run_wizard.h src/interface/widgets/onboarding/first_run_wizard_page.cpp @@ -531,7 +530,6 @@ qt6_add_shaders( "src/interface/widgets/onboarding/shaders" FILES src/interface/widgets/onboarding/shaders/brand_banner.frag - src/interface/widgets/onboarding/shaders/brand_plate.frag ) qt6_add_resources( diff --git a/cockatrice/cockatrice.qrc b/cockatrice/cockatrice.qrc index 14cf15b2f..e21bdb0be 100644 --- a/cockatrice/cockatrice.qrc +++ b/cockatrice/cockatrice.qrc @@ -63,8 +63,6 @@ resources/icons/mana/W.svg resources/backgrounds/home.png - resources/backgrounds/home-dark.png - resources/backgrounds/home-light.png resources/backgrounds/card_triplet.svg resources/backgrounds/placeholder_printing_selector.svg @@ -365,8 +363,6 @@ resources/usericons/pawn_single.svg resources/usericons/pawn_double.svg - resources/usericons/pawn_dev_single.svg - resources/usericons/pawn_dev_double.svg resources/usericons/pawn_donator_single.svg resources/usericons/pawn_donator_double.svg resources/usericons/pawn_judge_single.svg diff --git a/cockatrice/resources/backgrounds/home-dark.png b/cockatrice/resources/backgrounds/home-dark.png deleted file mode 100644 index 68f48e2c2..000000000 Binary files a/cockatrice/resources/backgrounds/home-dark.png and /dev/null differ diff --git a/cockatrice/resources/backgrounds/home-light.png b/cockatrice/resources/backgrounds/home-light.png deleted file mode 100644 index eaaaba932..000000000 Binary files a/cockatrice/resources/backgrounds/home-light.png and /dev/null differ diff --git a/cockatrice/resources/backgrounds/home.png b/cockatrice/resources/backgrounds/home.png index eaaaba932..68f48e2c2 100644 Binary files a/cockatrice/resources/backgrounds/home.png and b/cockatrice/resources/backgrounds/home.png differ diff --git a/cockatrice/resources/usericons/pawn_dev_double.svg b/cockatrice/resources/usericons/pawn_dev_double.svg deleted file mode 100644 index 57ed5c2da..000000000 --- a/cockatrice/resources/usericons/pawn_dev_double.svg +++ /dev/null @@ -1,343 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/cockatrice/resources/usericons/pawn_dev_single.svg b/cockatrice/resources/usericons/pawn_dev_single.svg deleted file mode 100644 index f7c4e7018..000000000 --- a/cockatrice/resources/usericons/pawn_dev_single.svg +++ /dev/null @@ -1,211 +0,0 @@ - - - -image/svg+xml - - diff --git a/cockatrice/src/game_graphics/board/abstract_card_item.cpp b/cockatrice/src/game_graphics/board/abstract_card_item.cpp index 3969b7d03..1410d0c80 100644 --- a/cockatrice/src/game_graphics/board/abstract_card_item.cpp +++ b/cockatrice/src/game_graphics/board/abstract_card_item.cpp @@ -1,7 +1,6 @@ #include "abstract_card_item.h" #include "../../client/settings/cache_settings.h" -#include "../../interface/card_localization.h" #include "../../interface/card_picture_loader/card_picture_loader.h" #include "../game_scene.h" #include "../z_values.h" @@ -27,8 +26,6 @@ AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, const CardRef &cardRef connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::displayCardNamesChanged, this, [this] { update(); }); - connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardLangChanged, this, - [this] { update(); }); refreshCardInfo(); connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::roundCardCornersChanged, this, @@ -174,7 +171,7 @@ void AbstractCardItem::paintPicture(QPainter *painter, const QSizeF &translatedS if (SettingsCache::instance().debug().getShowCardId()) { prefix = "#" + QString::number(id) + " "; } - nameStr = prefix + CardLocalization::displayName(getCardInfo()); + nameStr = prefix + cardRef.name; } painter->drawText(QRectF(3 * scaleFactor, 3 * scaleFactor, translatedSize.width() - 6 * scaleFactor, translatedSize.height() - 6 * scaleFactor), diff --git a/cockatrice/src/interface/card_localization.h b/cockatrice/src/interface/card_localization.h deleted file mode 100644 index 0bfe4a764..000000000 --- a/cockatrice/src/interface/card_localization.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef COCKATRICE_CARD_LOCALIZATION_H -#define COCKATRICE_CARD_LOCALIZATION_H - -#include "../client/settings/cache_settings.h" - -#include -#include -#include - -namespace CardLocalization -{ -/** - * @brief The language code selected for localized card text and images. - */ -inline QString displayLang() -{ - return SettingsCache::instance().cardsDisplay().getCardLang(); -} - -/** - * @brief Card name in the configured display language, falling back to English. - * @param card The card to display. - * @return The localized name, or an empty string for a null card. - */ -inline QString displayName(const CardInfoPtr &card) -{ - return card.isNull() ? QString() : card->getLocalizedName(displayLang()); -} - -/** - * @brief Card rules text in the configured display language, falling back to English. - * @param card The card to display. - * @return The localized text, or an empty string for a null card. - */ -inline QString displayText(const CardInfoPtr &card) -{ - return card.isNull() ? QString() : card->getLocalizedText(displayLang()); -} - -/** - * @brief Card name in the configured display language, falling back to English. - * @param card The card to display. - */ -inline QString displayName(const CardInfo &card) -{ - return card.getLocalizedName(displayLang()); -} - -/** - * @brief Card rules text in the configured display language, falling back to English. - * @param card The card to display. - */ -inline QString displayText(const CardInfo &card) -{ - return card.getLocalizedText(displayLang()); -} -} // namespace CardLocalization - -#endif // COCKATRICE_CARD_LOCALIZATION_H \ No newline at end of file diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp index b8a54761a..8c81d641d 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include #include @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -38,10 +37,8 @@ CardPictureLoader::CardPictureLoader() : QObject(nullptr) &CardPictureLoader::picsPathChanged); connect(&SettingsCache::instance().downloads(), &DownloadSettings::picDownloadChanged, this, &CardPictureLoader::picDownloadChanged); - connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardLangChanged, this, - &CardPictureLoader::cardLangChanged); - qRegisterMetaType("ExactCard"); + qRegisterMetaType(); connect(worker, &CardPictureLoaderWorker::imageLoaded, this, &CardPictureLoader::imageLoaded); statusBar = new CardPictureLoaderStatusBar(nullptr); @@ -209,49 +206,7 @@ void CardPictureLoader::imageLoaded(const ExactCard &card, const QImage &image) card.emitPixmapUpdated(); } -void CardPictureLoader::deleteAllLocalOverrides(const ExactCard &card) -{ - const QString picsRoot = SettingsCache::instance().paths().getPicsPath(); - if (picsRoot.isEmpty() || !card) { - return; - } - - QDir baseDir(picsRoot); - if (!baseDir.cd("downloadedPics")) { - return; - } - - const QString name = card.getInfo().getCorrectedName(); - - QString set, collector, uuid; - auto printing = card.getPrinting(); - if (printing.getSet()) { - set = printing.getSet()->getCorrectedShortName(); - collector = printing.getProperty("num"); - uuid = printing.getUuid(); - } - - for (const auto &scheme : CardPictureLoaderLocalSchemes::exportSchemes()) { - QString rel = CardPictureLoaderLocalSchemes::expandPattern(scheme.pattern, name, set, collector, uuid); - - if (rel.isEmpty()) { - continue; - } - - rel += ".png"; - rel = QDir::cleanPath(rel); - - QString fullPath = baseDir.filePath(rel); - - if (QFile::exists(fullPath)) { - QFile::remove(fullPath); - } - } -} - -void CardPictureLoader::saveCardImageToLocalStorage(const ExactCard &card, - const QPixmap &pixmap, - const bool allowOverwrite) +void CardPictureLoader::saveCardImageToLocalStorage(const ExactCard &card, const QPixmap &pixmap) { if (pixmap.isNull() || !card) { return; @@ -311,9 +266,8 @@ void CardPictureLoader::saveCardImageToLocalStorage(const ExactCard &card, QFileInfo outInfo(baseDir.filePath(relativePath)); - // Automatic cache writes (FILESYSTEM_CACHE) must never clobber an explicit user override. - // Only the explicit override paths pass allowOverwrite == true. - if (!allowOverwrite && outInfo.exists()) { + // Do not overwrite existing files + if (outInfo.exists()) { return; } @@ -334,122 +288,6 @@ void CardPictureLoader::saveCardImageToLocalStorage(const ExactCard &card, } } -void CardPictureLoader::installPrintingOverrideOnLoad(const ExactCard &originalCard, const ExactCard &overrideCard) -{ - // Overriding a card with itself is the reset case, not a real override: every code path below - // would re-enter itself through emitPixmapUpdated(). Reject it outright. - if (originalCard == overrideCard) { - return; - } - - CardInfoPtr cardPtr = overrideCard.getCardPtr(); - if (!cardPtr) { - return; - } - - // Heap-allocate so the lambda can capture it before the connection is made - auto *connectionHandle = new QMetaObject::Connection; - - *connectionHandle = - connect(cardPtr.data(), &CardInfo::pixmapUpdated, cardPtr.data(), - [originalCard, overrideCard, connectionHandle, this](const PrintingInfo &printing) { - // All printings share the same CardInfo, so ignore updates triggered by any - // other printing (e.g., the original card re-loading from disk). - if (printing != overrideCard.getPrinting()) { - return; - } - - QPixmap pixmap; - if (QPixmapCache::find(overrideCard.getPixmapCacheKey(), &pixmap) && !pixmap.isNull()) { - // The override art has resolved — persist it and reflect it immediately. - // Retire the connection before emitting so the refresh can't re-enter. - saveCardImageToLocalStorage(originalCard, pixmap, /*allowOverwrite=*/true); - - QObject::disconnect(*connectionHandle); - delete connectionHandle; - - QPixmapCache::clear(); - originalCard.emitPixmapUpdated(); - return; - } - - // The art could not be resolved. Keep the connection armed so a late resolution - // still lands, and surface a visible refusal instead of a silent no-op. An - // override already on disk is left untouched and simply re-displayed. - QPixmapCache::clear(); - if (!hasLocalOverrides(originalCard)) { - QPixmap refusedPixmap; - getCardBackLoadingFailedPixmap(refusedPixmap, QSize(480, 672)); - QPixmapCache::insert(originalCard.getPixmapCacheKey(), refusedPixmap); - } - originalCard.emitPixmapUpdated(); - }); - - // Now enqueue; if the image is already loading (deduplicated in the worker), - // the signal will still fire when it completes - CardPictureLoader::getInstance().worker->enqueueImageLoad(overrideCard); -} - -void CardPictureLoader::installPrintingOverride(const ExactCard &originalCard, const ExactCard &overrideCard) -{ - // Same guard as installPrintingOverrideOnLoad: self-override is the reset case. - if (originalCard == overrideCard) { - return; - } - - QPixmap pixmap; - const QString key = overrideCard.getPixmapCacheKey(); - - if (QPixmapCache::find(key, &pixmap) && !pixmap.isNull()) { - // Already cached — save immediately; the caller refreshes the card. - saveCardImageToLocalStorage(originalCard, pixmap, /*allowOverwrite=*/true); - return; - } - - // Cache miss or previously failed load — enqueue load and wait for the signal. - installPrintingOverrideOnLoad(originalCard, overrideCard); -} - -bool CardPictureLoader::hasLocalOverrides(const ExactCard &card) -{ - const QString picsRoot = SettingsCache::instance().paths().getPicsPath(); - if (picsRoot.isEmpty() || !card) { - return false; - } - - QDir baseDir(picsRoot); - if (!baseDir.cd("downloadedPics")) { - return false; - } - - const QString name = card.getInfo().getCorrectedName(); - - QString set, collector, uuid; - const PrintingInfo printing = card.getPrinting(); - if (printing.getSet()) { - set = printing.getSet()->getCorrectedShortName(); - collector = printing.getProperty("num"); - uuid = printing.getUuid(); - } - - for (const auto &scheme : CardPictureLoaderLocalSchemes::exportSchemes()) { - QString rel = CardPictureLoaderLocalSchemes::expandPattern(scheme.pattern, name, set, collector, uuid); - - if (rel.isEmpty()) { - continue; - } - - rel += ".png"; - rel = QDir::cleanPath(rel); - - if (QFile::exists(baseDir.filePath(rel))) { - return true; - } - } - - return false; -} - void CardPictureLoader::clearPixmapCache() { QPixmapCache::clear(); @@ -489,11 +327,31 @@ void CardPictureLoader::picsPathChanged() QPixmapCache::clear(); } -void CardPictureLoader::cardLangChanged() +bool CardPictureLoader::hasCustomArt() { - // Localized images are fetched via a different URL, but the in-memory - // pixmap cache is keyed by card name/uuid, so drop everything cached - // (including failure timestamps) to force a reload in the new language. - QPixmapCache::clear(); - failedAt.clear(); + auto picsPath = SettingsCache::instance().paths().getPicsPath(); + QDirIterator it(picsPath, QDir::Dirs | QDir::NoDotAndDotDot); + + // Check if there is at least one non-directory file in the pics path, other + // than in the "downloadedPics" subdirectory. + while (it.hasNext()) { +#if (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)) + QFileInfo dir(it.nextFileInfo()); +#else + // nextFileInfo() is only available in Qt 6.3+, for previous versions, we build + // the QFileInfo from a QString which requires more system calls. + QFileInfo dir(it.next()); +#endif + + if (it.fileName() == "downloadedPics") { + continue; + } + + QDirIterator subIt(it.filePath(), QDir::Files, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); + if (subIt.hasNext()) { + return true; + } + } + + return false; } diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader.h b/cockatrice/src/interface/card_picture_loader/card_picture_loader.h index 0a4934e6d..5c3ac84a3 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader.h +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader.h @@ -97,17 +97,10 @@ public: static void cacheCardPixmaps(const QList &cards); /** - * @brief Check if a local override image already exists for the card. - * @param card The card to check. - * @return True if the card has at least one locally stored override image. + * @brief Check if the user has custom card art in the picsPath directory. + * @return True if any custom art exists. */ - static bool hasLocalOverrides(const ExactCard &card); - - /** - * @brief Removes all locally stored override images for the card. - * @param card The card to remove the override images of. - */ - static void deleteAllLocalOverrides(const ExactCard &card); + static bool hasCustomArt(); /** * @brief Clears the in-memory QPixmap cache for all cards. @@ -127,9 +120,7 @@ public slots: * @param image Loaded QImage. */ void imageLoaded(const ExactCard &card, const QImage &image); - void saveCardImageToLocalStorage(const ExactCard &card, const QPixmap &pixmap, bool allowOverwrite = false); - void installPrintingOverride(const ExactCard &originalCard, const ExactCard &overrideCard); - void installPrintingOverrideOnLoad(const ExactCard &originalCard, const ExactCard &overrideCard); + void saveCardImageToLocalStorage(const ExactCard &card, const QPixmap &pixmap); private slots: /** @@ -143,12 +134,6 @@ private slots: * Clears the QPixmap cache to reload images. */ void picsPathChanged(); - - /** - * @brief Triggered when the card language setting changes. - * Clears the in-memory picture caches so images reload in the new language. - */ - void cardLangChanged(); }; #endif diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp index c82fca403..39621839a 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp @@ -94,10 +94,6 @@ QImage CardPictureLoaderLocal::tryLoadCardImageFromDisk(const QString &setName, candidatePaths << picsPath + "/downloadedPics/" + setName + "/" + nameVariant; } - // Non-set-folder export schemes (e.g., Name_Set_Collector) write straight into - // downloadedPics/; check there as a fallback so local overrides round-trip. - candidatePaths << picsPath + "/downloadedPics/" + nameVariant; - for (const QString &path : candidatePaths) { QFileInfo fileInfo(path); QDir dir = fileInfo.dir(); @@ -109,8 +105,7 @@ QImage CardPictureLoaderLocal::tryLoadCardImageFromDisk(const QString &setName, QStringList files = dir.entryList(QDir::Files); for (const QString &file : files) { - QFileInfo fi(file); - if (fi.completeBaseName() != baseName) { + if (!file.startsWith(baseName)) { continue; } diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_to_load.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_to_load.cpp index 7cb502e92..5f4ff0bbd 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_to_load.cpp +++ b/cockatrice/src/interface/card_picture_loader/card_picture_to_load.cpp @@ -94,8 +94,7 @@ void CardPictureToLoad::populateSetUrls() } } - const QStringList orderedTemplates = urlTemplates; - for (const QString &urlTemplate : orderedTemplates) { + for (const QString &urlTemplate : urlTemplates) { QString transformedUrl = transformUrl(urlTemplate); if (!transformedUrl.isEmpty()) { @@ -283,15 +282,8 @@ QString CardPictureToLoad::transformUrl(const QString &urlTemplate) const } // language setting - const QString cardLang = SettingsCache::instance().cardsDisplay().getCardLang(); - transformMap["!sflang!"] = cardLang; - - // The localized printing's own id is unknown, so Scryfall must resolve it by - // its translated name (see populateSetUrls); expose that name for the - // `/cards/named` template. - if (cardLang != "en") { - transformMap["!localizedName!"] = card.getInfo().getLocalizedName(cardLang); - } + transformMap["!sflang!"] = QString(QCoreApplication::translate( + "PictureLoader", "en", "code for scryfall's language property, not available for all languages")); QString transformedUrl = urlTemplate; for (const QString &prop : transformMap.keys()) { diff --git a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp index d5a168708..9cde72c01 100644 --- a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp +++ b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp @@ -1,5 +1,6 @@ #include "palette_editor_dialog.h" +#include "../../client/settings/cache_settings.h" #include "../theme_manager.h" #include "palette_generator.h" #include "palette_grid_widget.h" @@ -10,11 +11,31 @@ #include #include #include +#include #include +#include #include +#include #include #include +#include #include +#include + +// Probe whether a directory is truly writable by trying to create and remove a +// temporary file. QFileInfo::isWritable() on a directory is unreliable (notably +// on Windows where UAC VirtualStore can make a system dir appear writable). +static bool isDirReallyWritable(const QString &dirPath) +{ + const QString probe = QDir(dirPath).absoluteFilePath(".cockatrice_write_test"); + QFile f(probe); + if (!f.open(QIODevice::WriteOnly)) { + return false; + } + f.close(); + f.remove(); + return true; +} PaletteEditorDialog::PaletteEditorDialog(const QString &_themeDirPath, const QString &_themeName, QWidget *parent) : QDialog(parent), themeDirPath(_themeDirPath), themeName(_themeName) @@ -25,7 +46,14 @@ PaletteEditorDialog::PaletteEditorDialog(const QString &_themeDirPath, const QSt // Resolve a writable directory for saving. Built-in (Default / Fusion) and // other read-only theme directories must be customised in the user-writable // themes directory; otherwise the write would fail or be lost on upgrade. - saveDir = ThemeManager::writableThemeDir(themeName); + if (!themeDirPath.isEmpty() && isDirReallyWritable(themeDirPath)) { + saveDir = themeDirPath; + } else { + saveDir = QDir(SettingsCache::instance().paths().getThemesPath()).absoluteFilePath(themeName); + if (!QDir().mkpath(saveDir)) { + qWarning() << "Failed to create palette save directory:" << saveDir; + } + } // Load both scheme configs upfront so switching is instant loadSchemes(); @@ -186,7 +214,7 @@ void PaletteEditorDialog::retranslateUi() resetBtn->setToolTip(tr("Discard unsaved edits and restore the last saved palette")); saveBtn->setToolTip(tr("Write palette-%1.toml and reload the theme").arg(loadedScheme.toLower())); - if (saveDir.isEmpty() || !ThemeManager::isDirReallyWritable(saveDir)) { + if (saveDir.isEmpty() || !isDirReallyWritable(saveDir)) { saveBtn->setEnabled(false); saveBtn->setToolTip(tr("Cannot save: this theme has no writable directory")); } @@ -269,7 +297,7 @@ void PaletteEditorDialog::onSave() if (it.key() == loadedScheme) { continue; } - if (it.value() == savedConfig.value(it.key())) { + if (it.value().colors == savedConfig.value(it.key()).colors) { continue; } if (!ThemeManager::commitPalette(saveDir, it.key(), it.value())) { @@ -280,7 +308,7 @@ void PaletteEditorDialog::onSave() } // Commit the active scheme last so the global colour scheme matches. - if (workingConfig[loadedScheme] != savedConfig.value(loadedScheme)) { + if (workingConfig[loadedScheme].colors != savedConfig.value(loadedScheme).colors) { if (!ThemeManager::commitPalette(saveDir, loadedScheme, workingConfig[loadedScheme])) { QMessageBox::warning(this, tr("Save failed"), tr("Could not write %1 to:\n%2").arg(PaletteConfig::fileName(loadedScheme), saveDir)); diff --git a/cockatrice/src/interface/palette_editor/palette_generator.cpp b/cockatrice/src/interface/palette_editor/palette_generator.cpp index 822e57250..d30dd14f1 100644 --- a/cockatrice/src/interface/palette_editor/palette_generator.cpp +++ b/cockatrice/src/interface/palette_editor/palette_generator.cpp @@ -150,17 +150,6 @@ PaletteConfig fromAccent(const QColor &accent, int intensity, const QString &sch cfg.colors[CG::Disabled][CR::HighlightedText] = disText; cfg.colors[CG::Inactive][CR::HighlightedText] = dark ? Qt::white : Qt::black; - // Accent: same primary hue as Highlight, so palettes derived from a - // QuickSetup accent always carry a matching Accent role. -#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) - set3(CR::Accent, hl, disText, hl); -#endif - - // Application role colors: Strong tracks the primary accent, while Soft is - // the lightened, desaturated companion used for button-gradient highlights. - cfg.appColors[AppColor::AccentStrong] = hl; - cfg.appColors[AppColor::AccentSoft] = hsl(accent.lightness() + 60, qRound(accent.hslSaturation() * 70 / 100.0)); - // BrightText QColor bright; if (achromatic) { diff --git a/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp b/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp index 97d28b731..67294cd98 100644 --- a/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp +++ b/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp @@ -1,7 +1,5 @@ #include "palette_grid_widget.h" -#include "../theme_manager.h" - #include #include #include @@ -47,11 +45,6 @@ static const QMap ROLE_DESCRIPTIONS = { {QPalette::Shadow, QT_TR_NOOP("Very dark shadow colour")}, }; -static const QMap APP_ROLE_DESCRIPTIONS = { - {AppColor::AccentStrong, QT_TR_NOOP("Vivid primary accent (e.g. home-tab button gradient start)")}, - {AppColor::AccentSoft, QT_TR_NOOP("Lightened, desaturated accent (e.g. home-tab button gradient end)")}, -}; - PaletteGridWidget::PaletteGridWidget(QWidget *parent) : QWidget(parent) { scroll = new QScrollArea(this); @@ -129,46 +122,6 @@ void PaletteGridWidget::buildGrid(QWidget *host) grid->addWidget(btn, row + 1, col + 1, Qt::AlignHCenter | Qt::AlignVCenter); } } - - // Application color section: one ColorButton per role below the role grid. - // These are not tied to a color group, so a single button spans the row. - QMetaEnum appEnum = QMetaEnum::fromType(); - - const int appHeaderRow = roles.size() + 1; - - auto *appHeader = new QLabel(tr("App colors"), host); - appHeader->setToolTip(tr("Application-specific colors layered on top of the Qt palette")); - QFont appHeaderFont = appHeader->font(); - appHeaderFont.setBold(true); - appHeader->setFont(appHeaderFont); - appHeader->setAutoFillBackground(true); - appHeader->setContentsMargins(4, 4, 4, 4); - grid->addWidget(appHeader, appHeaderRow, 0, 1, 4); - headerLabels.append(appHeader); - - for (int i = 0; i < appEnum.keyCount(); ++i) { - auto role = static_cast(appEnum.value(i)); - const int row = appHeaderRow + 1 + i; - - if (i % 2 == 0) { - for (int col = 0; col < 4; ++col) { - auto *shade = new QWidget(host); - shade->setAutoFillBackground(true); - grid->addWidget(shade, row, col); - rowShadeWidgets.push_back(shade); - } - } - - auto *label = new QLabel(QString(appEnum.valueToKey(role)), host); - label->setToolTip(APP_ROLE_DESCRIPTIONS.value(role, {})); - label->setContentsMargins(4, 2, 8, 2); - grid->addWidget(label, row, 0); - - auto *btn = new ColorButton(host); - connect(btn, &ColorButton::colorChanged, this, [this] { emit paletteChanged(); }); - appColorButtons[role] = btn; - grid->addWidget(btn, row, 1, Qt::AlignHCenter | Qt::AlignVCenter); - } } void PaletteGridWidget::changeEvent(QEvent *e) @@ -213,16 +166,6 @@ void PaletteGridWidget::loadPalette(const PaletteConfig &cfg) colorButtons[group][role]->setColor(color); } } - - QMetaEnum appEnum = QMetaEnum::fromType(); - for (int i = 0; i < appEnum.keyCount(); ++i) { - auto role = static_cast(appEnum.value(i)); - QColor color = cfg.appColors.value(role); - if (!color.isValid()) { - color = themeManager->appColor(role); - } - appColorButtons[role]->setColor(color); - } } PaletteConfig PaletteGridWidget::currentPaletteConfig() const @@ -233,12 +176,5 @@ PaletteConfig PaletteGridWidget::currentPaletteConfig() const cfg.colors[group][role] = colorButtons[group][role]->getColor(); } } - - QMetaEnum appEnum = QMetaEnum::fromType(); - for (int i = 0; i < appEnum.keyCount(); ++i) { - auto role = static_cast(appEnum.value(i)); - cfg.appColors[role] = appColorButtons[role]->getColor(); - } - return cfg; } \ No newline at end of file diff --git a/cockatrice/src/interface/palette_editor/palette_grid_widget.h b/cockatrice/src/interface/palette_editor/palette_grid_widget.h index 77cbf1c62..1a665971a 100644 --- a/cockatrice/src/interface/palette_editor/palette_grid_widget.h +++ b/cockatrice/src/interface/palette_editor/palette_grid_widget.h @@ -31,7 +31,6 @@ private: void refreshChromePalettes(); QMap> colorButtons; - QMap appColorButtons; QScrollArea *scroll; QWidget *gridHost; QVBoxLayout *layout; diff --git a/cockatrice/src/interface/pixel_map_generator.cpp b/cockatrice/src/interface/pixel_map_generator.cpp index b70dc576f..e74e86471 100644 --- a/cockatrice/src/interface/pixel_map_generator.cpp +++ b/cockatrice/src/interface/pixel_map_generator.cpp @@ -16,6 +16,7 @@ #define DEFAULT_COLOR_MODERATOR_LEFT "#ffffff"; #define DEFAULT_COLOR_MODERATOR_RIGHT "#000000"; #define DEFAULT_COLOR_ADMIN "#ff2701"; +#define DEFAULT_COLOR_DEVELOPER "#B8B8B8" /** * Clamps an svg render size so that rendering does not exceed a multiple of the requested size. @@ -361,10 +362,6 @@ static QString getIconType(const bool isBuddy, const UserLevelFlags &userLevelFl return "pawn_judge"; } - if (userLevelFlags.testFlag(ServerInfo_User::IsDeveloper)) { - return "pawn_dev"; - } - if (!privLevel.isEmpty() && privLevel.toLower() != "none") { return QString("pawn_%1").arg(privLevel.toLower()); } @@ -385,6 +382,8 @@ QIcon UserLevelPixmapGenerator::generateIconDefault(int height, if (userLevel.testFlag(ServerInfo_User::IsAdmin)) { colorLeft = DEFAULT_COLOR_ADMIN; + } else if (userLevel.testFlag(ServerInfo_User::IsDeveloper)) { + colorLeft = DEFAULT_COLOR_DEVELOPER; } else if (userLevel.testFlag(ServerInfo_User::IsModerator)) { colorLeft = DEFAULT_COLOR_MODERATOR_LEFT; colorRight = DEFAULT_COLOR_MODERATOR_RIGHT; diff --git a/cockatrice/src/interface/theme_config.cpp b/cockatrice/src/interface/theme_config.cpp index 8293a82cf..3c43c467d 100644 --- a/cockatrice/src/interface/theme_config.cpp +++ b/cockatrice/src/interface/theme_config.cpp @@ -16,7 +16,7 @@ QString ThemeConfig::toIni() const out += "[Appearance]\n"; out += QString("ColorScheme = %1\n").arg(colorScheme.isEmpty() ? "System" : colorScheme); out += "\n[Style]\n"; - out += QString("Name = %1\n").arg(styleName.isEmpty() ? "System" : styleName); + out += QString("Name = %1\n").arg(styleName.isEmpty() ? "Default" : styleName); return out; } @@ -96,7 +96,7 @@ bool ThemeConfig::save(const QString &themeDirPath) const bool PaletteConfig::hasPalette() const { - return !colors.isEmpty() || !appColors.isEmpty(); + return !colors.isEmpty(); } QString PaletteConfig::toToml() const @@ -133,24 +133,6 @@ QString PaletteConfig::toToml() const out += "\n"; } - if (!appColors.isEmpty()) { - QMetaEnum appEnum = QMetaEnum::fromType(); - - out += "[AppColors]\n"; - - for (auto it = appColors.cbegin(); it != appColors.cend(); ++it) { - const char *roleName = appEnum.valueToKey(it.key()); - - if (!roleName) { - continue; - } - - out += QString("%1 = %2\n").arg(QString(roleName), -20).arg(it.value().name(QColor::HexArgb)); - } - - out += "\n"; - } - return out; } @@ -170,7 +152,6 @@ PaletteConfig PaletteConfig::fromFile(const QString &filePath) } QMetaEnum roleEnum = QMetaEnum::fromType(); - QMetaEnum appEnum = QMetaEnum::fromType(); QString currentSection; QPalette::ColorGroup currentGroup = QPalette::Active; @@ -221,26 +202,6 @@ PaletteConfig PaletteConfig::fromFile(const QString &filePath) } } - QColor color(value); - - if (!color.isValid()) { - continue; - } - - if (currentSection.compare("AppColors", Qt::CaseInsensitive) == 0) { - if (key.startsWith("AppColor::")) { - key = key.mid(10); - } - - int appRoleInt = appEnum.keyToValue(key.toUtf8().constData()); - - if (appRoleInt >= 0) { - cfg.appColors[static_cast(appRoleInt)] = color; - } - - continue; - } - if (!currentSection.startsWith("Palette", Qt::CaseInsensitive)) { continue; } @@ -255,7 +216,11 @@ PaletteConfig PaletteConfig::fromFile(const QString &filePath) continue; } - cfg.colors[currentGroup][static_cast(roleInt)] = color; + QColor color(value); + + if (color.isValid()) { + cfg.colors[currentGroup][static_cast(roleInt)] = color; + } } return cfg; diff --git a/cockatrice/src/interface/theme_config.h b/cockatrice/src/interface/theme_config.h index 567aeccda..07bf55b7a 100644 --- a/cockatrice/src/interface/theme_config.h +++ b/cockatrice/src/interface/theme_config.h @@ -3,25 +3,9 @@ #include #include -#include #include #include -// Application-specific color roles, layered on top of the fixed QPalette role -// set. Stored in the same palette-.toml under an [AppColors] section -// and editable from the palette editor, so theme authors can control colors -// beyond what Qt's palette can express. -namespace AppColor -{ -Q_NAMESPACE -enum Role -{ - AccentStrong, - AccentSoft, -}; -Q_ENUM_NS(Role) -} // namespace AppColor - struct ThemeConfig { QString colorScheme; @@ -37,16 +21,7 @@ struct ThemeConfig struct PaletteConfig { QMap> colors; - QMap appColors; - bool operator==(const PaletteConfig &rhs) const - { - return colors == rhs.colors && appColors == rhs.appColors; - } - bool operator!=(const PaletteConfig &rhs) const - { - return !(*this == rhs); - } bool hasPalette() const; QString toToml() const; diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index d86ed77f9..12c8fad2c 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -22,7 +21,7 @@ #include #include -#define SYSTEM_THEME_NAME "System" +#define NONE_THEME_NAME "Default" #define FUSION_THEME_NAME "Fusion" #define STYLE_CSS_NAME "style.css" #define HANDZONE_BG_NAME "handzone" @@ -96,7 +95,7 @@ struct PaletteColorInfo static QString usableDefaultStyle(const QString &style) { // The Windows 11 native style is broken: when the OS default - // ("System" theme selection) would use it, fall back to the Vista style. + // ("Default" theme selection) would use it, fall back to the Vista style. // Explicitly choosing "windows11" in a theme is still honored. return style.compare("windows11", Qt::CaseInsensitive) == 0 ? QStringLiteral("windowsvista") : style; } @@ -119,16 +118,10 @@ ThemeManager::ThemeManager(QObject *parent) : QObject(parent) void ThemeManager::ensureThemeDirectoryExists() { - auto &settings = SettingsCache::instance(); - - // Migrate the old "Default" theme name to "System" - if (settings.getThemeName() == "Default") { - settings.setThemeName(SYSTEM_THEME_NAME); - } - - if (settings.getThemeName().isEmpty() || !getAvailableThemes().contains(settings.getThemeName())) { + if (SettingsCache::instance().getThemeName().isEmpty() || + !getAvailableThemes().contains(SettingsCache::instance().getThemeName())) { qCInfo(ThemeManagerLog) << "Theme name not set, setting default value"; - settings.setThemeName(FUSION_THEME_NAME); + SettingsCache::instance().setThemeName(NONE_THEME_NAME); } } @@ -191,32 +184,11 @@ QString ThemeManager::assetPath(QStringView prefix) const return resolvedPlain.isEmpty() ? prefix.toString() : resolvedPlain; } -// Probe whether a directory is truly writable by trying to create and remove a -// temporary file. QFileInfo::isWritable() on a directory is unreliable (notably -// on Windows where UAC VirtualStore can make a system dir appear writable). -bool ThemeManager::isDirReallyWritable(const QString &dirPath) +bool ThemeManager::isBuiltInTheme() { - const QString probe = QDir(dirPath).absoluteFilePath(".cockatrice_write_test"); - QFile f(probe); - if (!f.open(QIODevice::WriteOnly)) { - return false; - } - f.close(); - f.remove(); - return true; -} + const auto themeName = SettingsCache::instance().getThemeName(); -QString ThemeManager::writableThemeDir(const QString &themeName) -{ - // All theme writes go to the user themes directory regardless of whether - // the resolved (system) theme directory happens to be writable. Even when a - // write would succeed in-place, routing it to the user directory keeps the - // install intact and guarantees changes survive upgrades. - const QString dirPath = QDir(SettingsCache::instance().paths().getThemesPath()).absoluteFilePath(themeName); - if (!QDir().mkpath(dirPath)) { - qWarning() << "Failed to create theme save directory:" << dirPath; - } - return dirPath; + return themeName == NONE_THEME_NAME || themeName == FUSION_THEME_NAME; } // System (read-only) themes location, relative to the application binary. @@ -241,7 +213,9 @@ QStringMap &ThemeManager::getAvailableThemes() // load themes from user profile dir dir.setPath(SettingsCache::instance().paths().getThemesPath()); - availableThemes.insert(SYSTEM_THEME_NAME, dir.absoluteFilePath("System")); + // add default value + availableThemes.insert(NONE_THEME_NAME, dir.absoluteFilePath("Default")); + availableThemes.insert(FUSION_THEME_NAME, dir.absoluteFilePath("Fusion")); for (QString themeName : dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) { @@ -357,7 +331,7 @@ bool ThemeManager::commitPalette(const QString &themeDirPath, const QString &col void ThemeManager::setColorScheme(const QString &scheme) { - const QString dirPath = writableThemeDir(SettingsCache::instance().getThemeName()); + const QString dirPath = getAvailableThemes().value(SettingsCache::instance().getThemeName()); ThemeConfig cfg = ThemeConfig::fromThemeDir(dirPath); cfg.colorScheme = scheme; @@ -368,7 +342,7 @@ void ThemeManager::setColorScheme(const QString &scheme) void ThemeManager::setStyleName(const QString &styleName) { - const QString dirPath = writableThemeDir(SettingsCache::instance().getThemeName()); + const QString dirPath = getAvailableThemes().value(SettingsCache::instance().getThemeName()); ThemeConfig cfg = ThemeConfig::fromThemeDir(dirPath); cfg.styleName = styleName; @@ -399,7 +373,7 @@ void ThemeManager::applyStyleAndPalette(const QString &themeName, Q_UNUSED(activeScheme) #endif QString styleName = themeCfg.styleName; - if (styleName.isEmpty() || styleName.compare("System", Qt::CaseInsensitive) == 0) { + if (styleName.isEmpty() || styleName.compare("Default", Qt::CaseInsensitive) == 0) { if (themeName == FUSION_THEME_NAME) { styleName = "Fusion"; } else { @@ -442,8 +416,6 @@ void ThemeManager::applyStyleAndPalette(const QString &themeName, qApp->setPalette(base); qApp->setStyle(style); - currentAppColors = palCfg.appColors; - // Force every widget to re-polish and repaint immediately rather than // waiting for natural expose events, which produces a patchwork of old // and new colours during a live preview. @@ -456,35 +428,6 @@ void ThemeManager::applyStyleAndPalette(const QString &themeName, style->polish(widget); widget->update(); } - - emit paletteChanged(); -} - -QColor ThemeManager::appColor(AppColor::Role role) const -{ - const auto it = currentAppColors.constFind(role); - if (it != currentAppColors.constEnd()) { - return it.value(); - } - - // QPalette::Accent was introduced in Qt 6.6 and several shipped palettes - // set it to a value barely distinguishable from Window, so it is not a - // reliable accent source. The selection highlight is the stable accent - // (Accent defaults to Highlight when unset), and deriving from it - // unconditionally keeps every Qt version rendering identically. - const QColor accent = qApp->palette().color(QPalette::Active, QPalette::Highlight); - - if (role == AppColor::AccentSoft) { - constexpr int SOFT_SATURATION_PERCENT = 70; - constexpr int SOFT_LIGHTNESS_OFFSET = 60; - - // Light end of the gradient: same hue, softened and lightened - return QColor::fromHsl(qMax(0, accent.hslHue()), - qBound(0, qRound(accent.hslSaturation() * SOFT_SATURATION_PERCENT / 100.0), 255), - qBound(0, accent.lightness() + SOFT_LIGHTNESS_OFFSET, 255)); - } - - return accent; } void ThemeManager::themeChangedSlot() @@ -521,19 +464,8 @@ void ThemeManager::themeChangedSlot() // ── Load palette: custom first, then theme default ──────────────────── PaletteConfig palette = PaletteConfig::fromScheme(dirPath, activeScheme); - const PaletteConfig themeDefault = ThemeManager::loadDefaultPaletteConfig(dirPath, themeName, activeScheme); - if (palette.hasPalette()) { - // A custom palette written before [AppColors] existed carries no app - // colors; merge the theme's shipped defaults so the identity colors - // survive (hasPalette() counts an app-colors-only file as a palette, - // so those are kept wholesale and never reach here empty). - for (auto it = themeDefault.appColors.cbegin(); it != themeDefault.appColors.cend(); ++it) { - if (!palette.appColors.contains(it.key())) { - palette.appColors.insert(it.key(), it.value()); - } - } - } else { - palette = themeDefault; + if (!palette.hasPalette()) { + palette = ThemeManager::loadDefaultPaletteConfig(dirPath, themeName, activeScheme); } applyStyleAndPalette(themeName, themeCfg, palette, activeScheme); diff --git a/cockatrice/src/interface/theme_manager.h b/cockatrice/src/interface/theme_manager.h index aadb38ee9..ac35042a0 100644 --- a/cockatrice/src/interface/theme_manager.h +++ b/cockatrice/src/interface/theme_manager.h @@ -50,7 +50,6 @@ private: QString currentThemePath; std::array brushes; QStringMap availableThemes; - QMap currentAppColors; /* Internal cache for multiple backgrounds */ @@ -66,16 +65,7 @@ protected: const QString &activeScheme); public: - // Resolves the directory to write theme changes to for the given theme - // name. The resolved theme dir (user or system) is used when writable; - // read-only system themes fall back to the user themes directory, creating - // it if needed, so customisations never get lost on upgrade. - static QString writableThemeDir(const QString &themeName); - // Probe whether a directory is truly writable by trying to create and remove - // a temporary file. QFileInfo::isWritable() on a directory is unreliable - // (notably on Windows where UAC VirtualStore can make a system dir appear - // writable). - static bool isDirReallyWritable(const QString &dirPath); + bool isBuiltInTheme(); // Explicit color scheme of the theme: theme.cfg's ColorScheme setting // (Dark/Light), falling back to the OS color scheme when it is "System". bool isDarkMode(const QString &themeDirPath) const; @@ -125,17 +115,12 @@ public: void reloadCurrentTheme(); void previewPalette(const PaletteConfig &cfg, const QString &scheme); - // Resolves an application color role: the theme's stored [AppColors] value - // when present, otherwise a palette-accent-derived fallback. - QColor appColor(AppColor::Role role) const; - QBrush &getBgBrush(Role zone); QBrush getExtraBgBrush(Role zone, int zoneId = 0); protected slots: void themeChangedSlot(); signals: void themeChanged(); - void paletteChanged(); }; extern ThemeManager *themeManager; diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_picture_widget.cpp index 2dd21e78a..de622bdc8 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_widget.cpp @@ -74,8 +74,6 @@ CardInfoPictureWidget::CardInfoPictureWidget(QWidget *parent, const bool _hoverT update(); }); - connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardLangChanged, this, - &CardInfoPictureWidget::updatePixmap); } /** diff --git a/cockatrice/src/interface/widgets/cards/card_info_text_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_text_widget.cpp index e98c3c02a..c6af5320b 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_text_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_text_widget.cpp @@ -1,7 +1,6 @@ #include "card_info_text_widget.h" #include "../../../game_graphics/board/card_item.h" -#include "../../card_localization.h" #include #include @@ -11,7 +10,7 @@ #include #include -CardInfoTextWidget::CardInfoTextWidget(QWidget *parent) : QFrame(parent) +CardInfoTextWidget::CardInfoTextWidget(QWidget *parent) : QFrame(parent), info(nullptr) { propsLabel = new QLabel; propsLabel->setOpenExternalLinks(false); @@ -40,12 +39,6 @@ CardInfoTextWidget::CardInfoTextWidget(QWidget *parent) : QFrame(parent) grid->setRowStretch(1, 1); retranslateUi(); - - connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardLangChanged, this, [this] { - if (currentCard) { - setCard(currentCard); - } - }); } void CardInfoTextWidget::setTexts(const QString &propsText, const QString &textText) @@ -67,7 +60,7 @@ void CardInfoTextWidget::setCard(const ExactCard &exactCard) QString text = ""; text += QString("") - .arg(tr("Name:"), CardLocalization::displayName(card).toHtmlEscaped()); + .arg(tr("Name:"), card->getName().toHtmlEscaped()); if (!exactCard.getPrinting().isEmpty()) { QString setShort = exactCard.getPrinting().getSet()->getShortName().toHtmlEscaped(); @@ -101,8 +94,7 @@ void CardInfoTextWidget::setCard(const ExactCard &exactCard) } text += "
%1%2
"; - setTexts(text, CardLocalization::displayText(card)); - currentCard = exactCard; + setTexts(text, card->getText()); } void CardInfoTextWidget::setInvalidCardName(const QString &cardName) diff --git a/cockatrice/src/interface/widgets/cards/card_info_text_widget.h b/cockatrice/src/interface/widgets/cards/card_info_text_widget.h index 683be5ab5..a9c29da37 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_text_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_info_text_widget.h @@ -23,7 +23,7 @@ private: QLabel *propsLabel; QScrollArea *propsScroll; QTextEdit *textLabel; - ExactCard currentCard; ///< Last card set, re-rendered when the card language changes. + CardInfoPtr info; void setTexts(const QString &propsText, const QString &textText); public: diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp index 4b141e255..a3653f03e 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp @@ -345,9 +345,7 @@ ExactCard DeckEditorDeckDockWidget::getCurrentCard() if (!current.isValid()) { return {}; } - // The display role holds the localized card name; the edit role always carries the - // canonical English name needed to look the card up in the database. - const QString cardName = current.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); + const QString cardName = current.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString(); const QString cardProviderID = current.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data().toString(); const QModelIndex gparent = current.parent().parent(); @@ -520,11 +518,8 @@ void DeckEditorDeckDockWidget::syncBannerCardComboBoxSelectionWithDeck() void DeckEditorDeckDockWidget::setSelectedIndex(const QModelIndex &newCardIndex, bool preserveWidgetFocus) { - const QModelIndex proxyIndex = proxy->mapFromSource(newCardIndex); - deckView->clearSelection(); - deckView->setCurrentIndex(proxyIndex); - deckView->scrollTo(proxyIndex); + deckView->setCurrentIndex(newCardIndex); recursiveExpand(newCardIndex); if (!preserveWidgetFocus) { diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp index cfe989e9f..e563729a4 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp @@ -1,20 +1,13 @@ #include "deck_state_manager.h" -#include "../../../client/settings/cache_settings.h" - #include #include #include -#include DeckStateManager::DeckStateManager(QObject *parent) : QObject(parent), deckList(QSharedPointer(new DeckList)), deckListModel(new DeckListModel(this, deckList)), historyManager(new DeckListHistoryManager(this)) { - deckListModel->setDisplayLanguage(SettingsCache::instance().cardsDisplay().getCardLang()); - connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardLangChanged, deckListModel, - [this](const QString &lang) { deckListModel->setDisplayLanguage(lang); }); - connect(historyManager, &DeckListHistoryManager::undoRedoStateChanged, this, [this] { setModified(true); emit historyChanged(); @@ -267,10 +260,7 @@ bool DeckStateManager::swapCardAtIndex(const QModelIndex &idx) return false; } - // The display role holds the localized card name; the edit role always carries the - // canonical English name needed to look the card up in the database. - QString displayCardName = idx.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString(); - QString cardName = idx.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); + QString cardName = idx.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString(); QString providerId = idx.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data().toString(); QModelIndex gparent = idx.parent().parent(); @@ -287,7 +277,7 @@ bool DeckStateManager::swapCardAtIndex(const QModelIndex &idx) QString reason = tr("Moved to %1 1 × \"%2\" (%3)") // .arg(otherZoneName) - .arg(displayCardName) + .arg(cardName) .arg(providerId); return modifyDeck(reason, [&idx, &cardName, &providerId, &otherZoneName](auto model) { @@ -301,8 +291,9 @@ bool DeckStateManager::removeCardAtIndex(const QModelIndex &idx) return false; } - QString reason = - tr("Removed \"%1\" (all copies)").arg(idx.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString()); + QString cardName = idx.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString(); + + QString reason = tr("Removed \"%1\" (all copies)").arg(cardName); return modifyDeck(reason, [&idx](auto model) { return model->removeRow(idx.row(), idx.parent()); }); } diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp index 581920dbc..fb559fc4b 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp @@ -416,11 +416,6 @@ void DlgSettings::setTab(int index) } } -AbstractSettingsPage *DlgSettings::page(SettingsPage which) const -{ - return pages.value(static_cast(which)); -} - void DlgSettings::updateLanguage() { qApp->removeTranslator(translator); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.h b/cockatrice/src/interface/widgets/dialogs/dlg_settings.h index 845c0b4d6..b700f7af9 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.h @@ -54,7 +54,6 @@ public: explicit DlgSettings(QWidget *parent = nullptr); void setTab(int index); - AbstractSettingsPage *page(SettingsPage which) const; private slots: void onTabClicked(int index); diff --git a/cockatrice/src/interface/widgets/general/home_tab_button_color.h b/cockatrice/src/interface/widgets/general/home_tab_button_color.h index b45bd7a92..1550b57e7 100644 --- a/cockatrice/src/interface/widgets/general/home_tab_button_color.h +++ b/cockatrice/src/interface/widgets/general/home_tab_button_color.h @@ -11,8 +11,8 @@ namespace HomeTabButtonColor */ enum Source { - FromThemeColors, ///< Use the theme's identity accent colors - FromBackground, ///< Extract colour from the background image + Automatic, ///< Extract color from background, or use theme color if no background + FromBackground, ///< Always extract color from background }; struct Entry @@ -23,7 +23,7 @@ struct Entry inline QList all() { - static QList entries = {{FromThemeColors, QT_TR_NOOP("From theme colors")}, + static QList entries = {{Automatic, QT_TR_NOOP("Automatic")}, {FromBackground, QT_TR_NOOP("Extract from background")}}; return entries; @@ -33,12 +33,12 @@ inline QList all() * Safely converts an int into the corresponding Source. * * @param value The int value - * @return The Source. Returns Source::FromThemeColors if the value is not within range + * @return The Source. Returns Source::Automatic if the value is not within range */ inline Source intToSource(int value) { if (value > FromBackground) { - return FromThemeColors; // default + return Automatic; // default } return static_cast(value); diff --git a/cockatrice/src/interface/widgets/general/home_widget.cpp b/cockatrice/src/interface/widgets/general/home_widget.cpp index 648d315f9..0d030b973 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.cpp +++ b/cockatrice/src/interface/widgets/general/home_widget.cpp @@ -11,8 +11,6 @@ #include "home_tab_button_color.h" #include -#include -#include #include #include #include @@ -23,7 +21,8 @@ #include HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor) - : QWidget(parent), tabSupervisor(_tabSupervisor), background(themePixmap(QStringLiteral("backgrounds/home"))) + : QWidget(parent), tabSupervisor(_tabSupervisor), background(themePixmap(QStringLiteral("backgrounds/home"))), + overlay(themePixmap(QStringLiteral("cockatrice"))) { layout = new QGridLayout(this); @@ -55,8 +54,6 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor) // Lambda is cleaner to read than overloading this connect(&SettingsCache::instance().appearance(), &AppearanceSettings::homeTabDisplayCardNameChanged, this, [this] { repaint(); }); - connect(&SettingsCache::instance().appearance(), &AppearanceSettings::homeTabBackgroundDimChanged, this, - [this] { repaint(); }); connect(&SettingsCache::instance(), &SettingsCache::themeChanged, this, &HomeWidget::initializeBackgroundFromSource); connect(&SettingsCache::instance(), &SettingsCache::themeChanged, this, @@ -64,18 +61,12 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor) // Scheme flips (light/dark/system with an OS switch) fire on themeManager, // not on SettingsCache::themeChanged, so re-resolve the variant background. connect(themeManager, &ThemeManager::themeChanged, this, &HomeWidget::initializeBackgroundFromSource); - connect(themeManager, &ThemeManager::paletteChanged, this, &HomeWidget::updateButtonsToBackgroundColor); - connect(themeManager, &ThemeManager::paletteChanged, this, &HomeWidget::updateLogoOverlay); connect(&SettingsCache::instance().appearance(), &AppearanceSettings::homeTabButtonColorChanged, this, &HomeWidget::updateButtonsToBackgroundColor); } void HomeWidget::initializeBackgroundFromSource() { - // The featured logo is theme/scheme-derived too; reload it alongside the - // background so a theme or appearance switch doesn't leave it stale. - updateLogoOverlay(); - if (CardDatabaseManager::getInstance()->getLoadStatus() != LoadStatus::Ok) { connect(CardDatabaseManager::getInstance(), &CardDatabase::cardDatabaseLoadingFinished, this, &HomeWidget::initializeBackgroundFromSource); @@ -114,24 +105,32 @@ void HomeWidget::loadBackgroundSourceDeck() backgroundSourceDeck = deckOpt.has_value() ? deckOpt.value().deckList : DeckList(); } -static QPair paletteDerivedButtonColors() +static bool isDefaultBackgroundAndTheme() { - return {themeManager->appColor(AppColor::AccentStrong), themeManager->appColor(AppColor::AccentSoft)}; + QString sourceId = SettingsCache::instance().appearance().getHomeTabBackgroundSource(); + return themeManager->isBuiltInTheme() && BackgroundSources::fromId(sourceId) == BackgroundSources::Theme; } QPair HomeWidget::determineButtonColor() const { + static QPair defaultColor = {QColor::fromRgb(20, 140, 60), QColor::fromRgb(120, 200, 80)}; + auto colorSource = HomeTabButtonColor::intToSource(SettingsCache::instance().appearance().getHomeTabButtonColorSourceIndex()); switch (colorSource) { - case HomeTabButtonColor::FromThemeColors: - return paletteDerivedButtonColors(); + case HomeTabButtonColor::Automatic: { + if (isDefaultBackgroundAndTheme()) { + return defaultColor; + } else { + return extractDominantColors(background); + } + } case HomeTabButtonColor::FromBackground: return extractDominantColors(background); } - return paletteDerivedButtonColors(); + return defaultColor; } void HomeWidget::setRandomCard(ExactCard &newCard) @@ -235,10 +234,10 @@ QGroupBox *HomeWidget::createButtons() QVBoxLayout *boxLayout = new QVBoxLayout; boxLayout->setAlignment(Qt::AlignHCenter); - logoLabel = new QLabel; + QLabel *logoLabel = new QLabel; + logoLabel->setPixmap(overlay.scaledToWidth(200, Qt::SmoothTransformation)); logoLabel->setAlignment(Qt::AlignCenter); boxLayout->addWidget(logoLabel); - updateLogoOverlay(); boxLayout->addSpacing(25); connectButton = new HomeStyledButton("Connect/Play", gradientColors); @@ -366,15 +365,13 @@ void HomeWidget::paintEvent(QPaintEvent *event) painter.drawPixmap(topLeft, toDraw); } - if (SettingsCache::instance().appearance().getHomeTabBackgroundDim()) { - // Draw translucent black overlay with rounded corners - QRectF overlayRect(5, 5, width() - 10, height() - 10); - QPainterPath roundedRectPath; - roundedRectPath.addRoundedRect(overlayRect, 20, 20); + // Draw translucent black overlay with rounded corners + QRectF overlayRect(5, 5, width() - 10, height() - 10); + QPainterPath roundedRectPath; + roundedRectPath.addRoundedRect(overlayRect, 20, 20); - QColor semiTransparentBlack(0, 0, 0, static_cast(255 * 0.33)); - painter.fillPath(roundedRectPath, semiTransparentBlack); - } + QColor semiTransparentBlack(0, 0, 0, static_cast(255 * 0.33)); + painter.fillPath(roundedRectPath, semiTransparentBlack); // Card name overlay (above the attribution, bottom-right) QString cardName; @@ -439,56 +436,3 @@ void HomeWidget::paintEvent(QPaintEvent *event) QWidget::paintEvent(event); } - -void HomeWidget::updateLogoOverlay() -{ - // Emulate cockatrice.svg in Qt rather than rendering the baked-in SVG. - // The SVG has no separate plate: the gradient fills the bird's silhouette - // paths (light #c9fd62/AccentSoft at the top-left, dark #139740/AccentStrong - // toward the bottom-right — the SVG's linearGradient4265-7-8 stops along - // its userSpaceOnUse axis), and the white highlight path - // (cockatrice-logo-white) sits on top. So we paint that gradient clipped to - // the full logo silhouette (the full-color logo's alpha), then overlay the - // white mark. Colours stay fully theme-driven and independent of the static - // greens baked into the SVG. - const QColor strong = themeManager->appColor(AppColor::AccentStrong); - const QColor soft = themeManager->appColor(AppColor::AccentSoft); - - const QPixmap silhouette = themePixmap(QStringLiteral("cockatrice")).scaledToWidth(200, Qt::SmoothTransformation); - const QPixmap whiteMark = - themePixmap(QStringLiteral("cockatrice-logo-white")).scaledToWidth(200, Qt::SmoothTransformation); - if (silhouette.isNull() || whiteMark.isNull()) { - return; - } - - QPixmap composite(silhouette.size()); - composite.fill(Qt::transparent); - - { - QPainter painter(&composite); - painter.setRenderHint(QPainter::Antialiasing); - painter.setRenderHint(QPainter::SmoothPixmapTransform); - - // Recreate cockatrice.svg's own gradient geometry (linearGradient - // 4265-7-8, userSpaceOnUse): light AccentSoft at S=(-8.097,-97.746), - // dark AccentStrong at E=(162.455,295.208), on the SVG's 300x300 - // canvas. Scale those coordinates to this composite's size. - const qreal scale = composite.width() / 300.0; - QLinearGradient gradient(QPointF(-8.097, -97.746) * scale, QPointF(162.455, 295.208) * scale); - gradient.setColorAt(0.0, soft); - gradient.setColorAt(1.0, strong); - painter.fillRect(composite.rect(), gradient); - - // Clip the gradient to the full logo silhouette exactly as the SVG's - // gradient paths are confined to the bird. - painter.setCompositionMode(QPainter::CompositionMode_DestinationIn); - painter.drawPixmap(0, 0, silhouette); - - painter.setCompositionMode(QPainter::CompositionMode_SourceOver); - painter.drawPixmap(0, 0, whiteMark); - } - - if (logoLabel) { - logoLabel->setPixmap(composite); - } -} diff --git a/cockatrice/src/interface/widgets/general/home_widget.h b/cockatrice/src/interface/widgets/general/home_widget.h index 1cadc4a67..9df0d7b6a 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.h +++ b/cockatrice/src/interface/widgets/general/home_widget.h @@ -15,9 +15,6 @@ #include #include -class QGridLayout; -class QLabel; - class HomeWidget : public QWidget { @@ -44,14 +41,13 @@ private: QPixmap background; CardInfoPictureArtCropWidget *backgroundSourceCard = nullptr; DeckList backgroundSourceDeck; - QLabel *logoLabel = nullptr; + QPixmap overlay; QPair gradientColors; HomeStyledButton *connectButton; void setRandomCard(ExactCard &newCard); void loadBackgroundSourceDeck(); QPair determineButtonColor() const; - void updateLogoOverlay(); }; #endif // HOME_WIDGET_H diff --git a/cockatrice/src/interface/widgets/onboarding/banner_shader_config.h b/cockatrice/src/interface/widgets/onboarding/banner_shader_config.h index 6008044ff..32f3e89c0 100644 --- a/cockatrice/src/interface/widgets/onboarding/banner_shader_config.h +++ b/cockatrice/src/interface/widgets/onboarding/banner_shader_config.h @@ -38,10 +38,6 @@ class BannerShaderConfig : public QObject Q_PROPERTY(QColor colorA READ colorA WRITE setColorA NOTIFY colorAChanged) Q_PROPERTY(QColor colorB READ colorB WRITE setColorB NOTIFY colorBChanged) Q_PROPERTY(QColor accent READ accent WRITE setAccent NOTIFY accentChanged) - Q_PROPERTY(QColor glowColor READ glowColor WRITE setGlowColor NOTIFY glowColorChanged) - Q_PROPERTY(QColor brandStrong READ brandStrong WRITE setBrandStrong NOTIFY brandStrongChanged) - Q_PROPERTY(QColor brandSoft READ brandSoft WRITE setBrandSoft NOTIFY brandSoftChanged) - Q_PROPERTY(qreal vignetteMin READ vignetteMin WRITE setVignetteMin NOTIFY vignetteMinChanged) Q_PROPERTY(bool logoVisible READ logoVisible WRITE setLogoVisible NOTIFY logoVisibleChanged) Q_PROPERTY(qreal logoGlow READ logoGlow WRITE setLogoGlow NOTIFY logoGlowChanged) @@ -189,54 +185,6 @@ public: } } - QColor glowColor() const - { - return m_glowColor; - } - void setGlowColor(const QColor &c) - { - if (c != m_glowColor) { - m_glowColor = c; - emit glowColorChanged(); - } - } - - QColor brandStrong() const - { - return m_brandStrong; - } - void setBrandStrong(const QColor &c) - { - if (c != m_brandStrong) { - m_brandStrong = c; - emit brandStrongChanged(); - } - } - - QColor brandSoft() const - { - return m_brandSoft; - } - void setBrandSoft(const QColor &c) - { - if (c != m_brandSoft) { - m_brandSoft = c; - emit brandSoftChanged(); - } - } - - qreal vignetteMin() const - { - return m_vignetteMin; - } - void setVignetteMin(qreal v) - { - if (v != m_vignetteMin) { - m_vignetteMin = v; - emit vignetteMinChanged(); - } - } - bool logoVisible() const { return m_logoVisible; @@ -274,10 +222,6 @@ signals: void colorAChanged(); void colorBChanged(); void accentChanged(); - void glowColorChanged(); - void brandStrongChanged(); - void brandSoftChanged(); - void vignetteMinChanged(); void logoVisibleChanged(); void logoGlowChanged(); @@ -295,16 +239,9 @@ private: bool m_frontIsA = true; - // Curated fallback seed values -- BannerHost overwrites these with - // palette-derived colours (see shader_banner_widget.cpp) before the first - // paint, so they only matter as a safe pre-first-apply default. QColor m_colorA{0x1A, 0x1A, 0x20}; QColor m_colorB{0x0E, 0x0E, 0x12}; QColor m_accent{0x8B, 0xDD, 0x6B}; - QColor m_glowColor{Qt::white}; - QColor m_brandStrong{0x13, 0x97, 0x40}; - QColor m_brandSoft{0xC9, 0xFD, 0x62}; - qreal m_vignetteMin = 0.62; bool m_logoVisible = false; qreal m_logoGlow = 1.0; diff --git a/cockatrice/src/interface/widgets/onboarding/brand_colors.h b/cockatrice/src/interface/widgets/onboarding/brand_colors.h deleted file mode 100644 index bf173270b..000000000 --- a/cockatrice/src/interface/widgets/onboarding/brand_colors.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef BRAND_COLORS_H -#define BRAND_COLORS_H - -#include - -/** @brief Cockatrice brand green. - * - * Single source of truth for the onboarding brand accent: it backs the - * banner's shader-accent uniform as the curated fallback when the active - * palette resolves no usable Highlight, and it preseads the wizard's - * QuickSetupPanel so a freshly generated palette keeps the brand identity - * until the user picks their own look. */ -inline const QColor kCockatriceBrandGreen(0x8B, 0xDD, 0x6B); - -#endif // BRAND_COLORS_H \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/onboarding/pages/theme_setup_page.cpp b/cockatrice/src/interface/widgets/onboarding/pages/theme_setup_page.cpp index 797fe4425..3293b19ac 100644 --- a/cockatrice/src/interface/widgets/onboarding/pages/theme_setup_page.cpp +++ b/cockatrice/src/interface/widgets/onboarding/pages/theme_setup_page.cpp @@ -14,31 +14,10 @@ #include #include #include -#include #include #include #include -namespace -{ -/** @brief A theme's shipped identity accent, immune to any user- or auto- - * generated palette that may currently be masking appColor(). */ -QColor themeIdentityAccent(const QString &themeDirPath, const QString &themeName) -{ - for (const QString &scheme : {QStringLiteral("Light"), QStringLiteral("Dark")}) { - const PaletteConfig cfg = ThemeManager::loadDefaultPaletteConfig(themeDirPath, themeName, scheme); - if (cfg.appColors.contains(AppColor::AccentStrong)) { - return cfg.appColors.value(AppColor::AccentStrong); - } - const QColor highlight = cfg.colors.value(QPalette::Active).value(QPalette::Highlight); - if (highlight.isValid()) { - return highlight; - } - } - return {}; -} -} // namespace - ThemeSetupPage::ThemeSetupPage(QWidget *parent) : FirstRunWizardPage(parent) { themeCombo = new QComboBox(this); @@ -51,15 +30,6 @@ ThemeSetupPage::ThemeSetupPage(QWidget *parent) : FirstRunWizardPage(parent) quickSetupPanel = new QuickSetupPanel(this); - // Seed the picker from the current theme's own identity accent rather than - // a hardcoded brand green: Plasma seeds violet, Fusion green, etc., and it - // is immune to stale generated palettes that may mask appColor(). This was - // initially a brand-green workaround from before Fusion became the default. - // setAccentColor blocks signals, so this never triggers a generation. - lastSeededTheme = SettingsCache::instance().getThemeName(); - quickSetupPanel->setAccentColor( - themeIdentityAccent(themeManager->getAvailableThemes().value(lastSeededTheme), lastSeededTheme)); - connect(themeCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &ThemeSetupPage::onThemeChanged); connect(schemeCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &ThemeSetupPage::onSchemeChanged); connect(quickSetupPanel, &QuickSetupPanel::valueChanged, this, &ThemeSetupPage::onGenerateFromAccent); @@ -77,8 +47,7 @@ ThemeSetupPage::ThemeSetupPage(QWidget *parent) : FirstRunWizardPage(parent) // Mirrors AppearanceSettingsPage's identical listener for the combo-sync // half of this. connect(themeManager, &ThemeManager::themeChanged, this, [this] { - const QString newTheme = SettingsCache::instance().getThemeName(); - const QString newDir = themeManager->getAvailableThemes().value(newTheme); + const QString newDir = themeManager->getAvailableThemes().value(SettingsCache::instance().getThemeName()); const ThemeConfig cfg = ThemeConfig::fromThemeDir(newDir); const QString current = cfg.colorScheme; @@ -87,14 +56,6 @@ ThemeSetupPage::ThemeSetupPage(QWidget *parent) : FirstRunWizardPage(parent) schemeCombo->setCurrentIndex(idx >= 0 ? idx : 0); schemeCombo->blockSignals(false); - // Keep the picker's accent in step with the theme's own identity; the - // swatch seeded at construction would otherwise stay stale (e.g. green - // from a previous theme) when the user toggles themes. - if (newTheme != lastSeededTheme) { - lastSeededTheme = newTheme; - quickSetupPanel->setAccentColor(themeIdentityAccent(newDir, newTheme)); - } - maybeAutoGeneratePalette(); }); @@ -197,14 +158,8 @@ void ThemeSetupPage::maybeAutoGeneratePalette() const QString dirPath = themeManager->getAvailableThemes().value(SettingsCache::instance().getThemeName()); const QString scheme = resolvedScheme(); - // The theme dir may resolve to the user profile even for built-in themes - // (getAvailableThemes gives the user copy precedence), so consult the - // shipped palette too -- both via loadDefaultPaletteConfig's system fallback. - // Without it, scheme flips regenerate a fresh palette from the picker accent - // and clobber the curated colours the theme explicitly ships. if (PaletteConfig::fromScheme(dirPath, scheme).hasPalette() || - ThemeManager::loadDefaultPaletteConfig(dirPath, SettingsCache::instance().getThemeName(), scheme) - .hasPalette()) { + PaletteConfig::fromDefault(dirPath, scheme).hasPalette()) { return; // theme already has something real to show -- leave it alone } diff --git a/cockatrice/src/interface/widgets/onboarding/pages/theme_setup_page.h b/cockatrice/src/interface/widgets/onboarding/pages/theme_setup_page.h index 16a3c9a5d..d1f84c1b9 100644 --- a/cockatrice/src/interface/widgets/onboarding/pages/theme_setup_page.h +++ b/cockatrice/src/interface/widgets/onboarding/pages/theme_setup_page.h @@ -53,9 +53,6 @@ private: QComboBox *homeTabBackgroundCombo; bool paletteDirty = false; - - /// Theme whose identity accent currently seeds the picker. - QString lastSeededTheme; }; #endif // THEME_SETUP_PAGE_H diff --git a/cockatrice/src/interface/widgets/onboarding/qml/BrandBanner.qml b/cockatrice/src/interface/widgets/onboarding/qml/BrandBanner.qml index d94e15280..f1a385cad 100644 --- a/cockatrice/src/interface/widgets/onboarding/qml/BrandBanner.qml +++ b/cockatrice/src/interface/widgets/onboarding/qml/BrandBanner.qml @@ -16,8 +16,6 @@ Item { property vector4d uColorA: Qt.vector4d(bannerConfig.colorA.r, bannerConfig.colorA.g, bannerConfig.colorA.b, 1.0) property vector4d uColorB: Qt.vector4d(bannerConfig.colorB.r, bannerConfig.colorB.g, bannerConfig.colorB.b, 1.0) property vector4d uAccent: Qt.vector4d(bannerConfig.accent.r, bannerConfig.accent.g, bannerConfig.accent.b, 1.0) - property vector4d uGlowColor: Qt.vector4d(bannerConfig.glowColor.r, bannerConfig.glowColor.g, bannerConfig.glowColor.b, 1.0) - property real uVignetteMin: bannerConfig.vignetteMin property real uLogoGlow: bannerConfig.logoGlow fragmentShader: "qrc:/onboarding/shaders/brand_banner.frag.qsb" } @@ -35,62 +33,30 @@ Item { property vector4d uColorA: Qt.vector4d(bannerConfig.colorA.r, bannerConfig.colorA.g, bannerConfig.colorA.b, 1.0) property vector4d uColorB: Qt.vector4d(bannerConfig.colorB.r, bannerConfig.colorB.g, bannerConfig.colorB.b, 1.0) property vector4d uAccent: Qt.vector4d(bannerConfig.accent.r, bannerConfig.accent.g, bannerConfig.accent.b, 1.0) - property vector4d uGlowColor: Qt.vector4d(bannerConfig.glowColor.r, bannerConfig.glowColor.g, bannerConfig.glowColor.b, 1.0) - property real uVignetteMin: bannerConfig.vignetteMin property real uLogoGlow: bannerConfig.logoGlow fragmentShader: "qrc:/onboarding/shaders/brand_banner.frag.qsb" } - // The white logo sits at full opacity on top of the static gradient plate — - // no glow, no breathing. The plate matches home_widget's QPainter composite. - Item { - id: logoHost - visible: bannerConfig.logoVisible + // The hero logo itself — breathes cleanly over a 0.5–1.0 opacity range + Image { + id: logo anchors.centerIn: parent + visible: bannerConfig.logoVisible + source: "qrc:/resources/cockatrice-logo-white.svg" width: root.height * 0.6 - height: width + height: width * (sourceSize.height > 0 ? sourceSize.height / Math.max(sourceSize.width, 1) : 1) + fillMode: Image.PreserveAspectFit + smooth: true + opacity: 0.5 + 0.5 * bannerConfig.logoGlow + sourceSize: Qt.size(256, 256) - // The full-color logo renders beneath the white mark and is consumed as - // a texture (layer.enabled) by the plate shader's silhouette mask, so - // the gradient is clipped to the bird exactly as the SVG's gradient - // paths are. It is never drawn to the screen itself. - Image { - id: silhouetteMask - anchors.fill: parent - source: "qrc:/resources/cockatrice.svg" - sourceSize: Qt.size(256, 256) - fillMode: Image.PreserveAspectFit - smooth: true - visible: false - layer.enabled: true - layer.smooth: true - } + Behavior on opacity { NumberAnimation { duration: 300; easing.type: Easing.InOutSine } } - // The logo's gradient plate, drawn behind the mark: a linear - // AccentSoft (light) -> AccentStrong (dark) sheet along the same - // top-left -> bottom-right userSpaceOnUse axis the baked-in SVG used, - // clipped to the bird silhouette via uSilhouette. The white highlight - // path above is theme independent. Sized to the logo itself — no - // rounded badge, matching home_widget's QPainter composite. Static. - // Small ShaderEffect, Qt 6.4-safe. - ShaderEffect { - id: brandPlate - anchors.fill: parent - property vector4d uStrong: Qt.vector4d(bannerConfig.brandStrong.r, bannerConfig.brandStrong.g, - bannerConfig.brandStrong.b, 1.0) - property vector4d uSoft: Qt.vector4d(bannerConfig.brandSoft.r, bannerConfig.brandSoft.g, - bannerConfig.brandSoft.b, 1.0) - property var uSilhouette: silhouetteMask - fragmentShader: "qrc:/onboarding/shaders/brand_plate.frag.qsb" - } - - Image { - id: logoImage - anchors.fill: parent - source: "qrc:/resources/cockatrice-logo-white.svg" - sourceSize: Qt.size(256, 256) - fillMode: Image.PreserveAspectFit - smooth: true + transform: Scale { + origin.x: logo.width / 2 + origin.y: logo.height / 2 + xScale: 0.94 + 0.06 * bannerConfig.logoGlow + yScale: 0.94 + 0.06 * bannerConfig.logoGlow } } -} \ No newline at end of file +} diff --git a/cockatrice/src/interface/widgets/onboarding/shader_banner_widget.cpp b/cockatrice/src/interface/widgets/onboarding/shader_banner_widget.cpp index 0b9f65783..fd1fb2a98 100644 --- a/cockatrice/src/interface/widgets/onboarding/shader_banner_widget.cpp +++ b/cockatrice/src/interface/widgets/onboarding/shader_banner_widget.cpp @@ -1,10 +1,7 @@ #include "shader_banner_widget.h" -#include "../../theme_manager.h" #include "banner_shader_config.h" -#include "brand_colors.h" -#include #include #include #include @@ -14,98 +11,11 @@ namespace { -// Curated near-black stage -- used only when the active palette resolves no -// usable window colour. Matches the banner's original design (dark and quiet -// so the accent stands out) and satisfies design-plans §2.1's "identity -// survives a bare palette". -constexpr QRgb kFallbackColorA = 0x1A1A20; -constexpr QRgb kFallbackColorB = 0x0E0E12; - -struct SuggestedColors -{ - QColor colorA; - QColor colorB; - QColor accent; - QColor glowColor; - QColor brandStrong; - QColor brandSoft; - qreal vignetteMin = 0.62; -}; - -SuggestedColors suggestedBannerColors() -{ - const QPalette &pal = qApp->palette(); - const QColor window = pal.color(QPalette::Active, QPalette::Window); - // Identity accent: the theme's [AppColors] AccentStrong, which appColor() - // resolves to QPalette::Highlight when a theme doesn't pin AccentStrong. - // Reading bare Highlight ignored curated accent tokens (Plasma's violet - // vs Default's green) whenever a palette didn't set the role itself. - const QColor accentStrong = themeManager->appColor(AppColor::AccentStrong); - if (!window.isValid() || !accentStrong.isValid()) { - return {QColor(kFallbackColorA), - QColor(kFallbackColorB), - kCockatriceBrandGreen, - QColor(Qt::white), - kCockatriceBrandGreen, - QColor(0xC9, 0xFD, 0x62), - 0.62}; - } - - // The theme's brand pair: AccentStrong is the deep green, AccentSoft the - // lime. These two appColors form the logo's "surrounding gradient" (deep - // core grading out to the soft, brand-toned glow) on both the banner and - // the home screen. - const QColor brandStrong = accentStrong; - const QColor brandSoft = themeManager->appColor(AppColor::AccentSoft); - - // Dress the stage for the scheme so the banner never fights the - // surrounding window in either mode. Dark palettes keep the original - // quiet near-black stage (lightness 29 → 16) with the theme's window - // hue; light palettes get a pastel "frosted accent" treatment built from - // the accent hue instead of a plain near-white copy: a coloured wash that - // clearly belongs to the theme. - const qreal luma = 0.299 * window.red() + 0.587 * window.green() + 0.114 * window.blue(); - const bool lightStage = luma > 115.0; - if (lightStage) { - const int hue = accentStrong.hslHue(); - // Achromatic accents (grey) get a neutral near-white stage instead. - const int stageSat = hue < 0 ? 0 : 64; - const int hueSafe = hue < 0 ? 0 : hue; - // Depth is what stops a light stage reading as a washed-out near-white - // copy of the page behind the banner: deepen the lower pastel band and - // raise saturation so the hue is clearly present while staying frosted. - auto pastel = [hueSafe, stageSat](int lightness) { return QColor::fromHsl(hueSafe, stageSat, lightness); }; - auto pastelLower = [hueSafe](int lightness) { return QColor::fromHsl(hueSafe, 76, lightness); }; - // Brightness-lifted accent for additive glows: the raw accent on a - // light stage must be mid-bright to read instead of washing out, so - // lift lightness and saturation together. - const int accentLightness = qBound(158, accentStrong.lightness() + 82, 198); - const int accentSaturation = hue < 0 ? 0 : qMax(accentStrong.hslSaturation(), 180); - const QColor liftedAccent = - hue < 0 ? accentStrong : QColor::fromHsl(hueSafe, accentSaturation, accentLightness); - // The centre glow (and logo tint in QML) uses the deep accent itself: - // a coloured halo/fill behind the logo instead of a white or black one. - return {pastel(214), pastelLower(186), liftedAccent, accentStrong, brandStrong, brandSoft, 0.80}; - } - - // Dark stage: force the window hue down to the banner's curated darkness, - // scaling saturation away so chromatic palettes tint it without going - // muddy. The accent is the bright, brand-driven tone (hue from the accent - // itself, never the -- often grey -- window), and it drives both the - // embers/fog and the logo glow so the mark tints like the light stage. - auto stage = [&window](int lightness) { - const int hue = window.hslHue(); - const int saturation = hue < 0 ? 0 : qBound(0, qRound(window.hslSaturation() * (lightness / 40.0)), 255); - return QColor::fromHsl(hue, saturation, lightness); - }; - const int accentHue = accentStrong.hslHue(); - const int accentHueSafe = accentHue < 0 ? 0 : accentHue; - const int accentLightness = qBound(150, accentStrong.lightness() + 70, 185); - const int accentSaturation = accentHue < 0 ? 0 : qMax(accentStrong.hslSaturation(), 160); - const QColor accent = - accentHue < 0 ? accentStrong : QColor::fromHsl(accentHueSafe, accentSaturation, accentLightness); - return {stage(29), stage(16), accent, accent, brandStrong, brandSoft, 0.62}; -} +// Near-black base palette -- the background is dark and quiet so the green +// accent stands out. +constexpr QRgb kColorA = 0x1A1A20; +constexpr QRgb kColorB = 0x0E0E12; +constexpr QRgb kAccent = 0x8BDD6B; } // namespace class GradientFallbackWidget : public QWidget @@ -113,27 +23,15 @@ class GradientFallbackWidget : public QWidget public: using QWidget::QWidget; - void setColors(const QColor &a, const QColor &b) - { - if (a != colorA || b != colorB) { - colorA = a; - colorB = b; - } - } - protected: void paintEvent(QPaintEvent *) override { QPainter painter(this); QLinearGradient gradient(0, 0, width(), height()); - gradient.setColorAt(0.0, colorA); - gradient.setColorAt(1.0, colorB); + gradient.setColorAt(0.0, QColor(kColorA)); + gradient.setColorAt(1.0, QColor(kColorB)); painter.fillRect(rect(), gradient); } - -private: - QColor colorA{QColor(kFallbackColorA)}; - QColor colorB{QColor(kFallbackColorB)}; }; BannerHost::BannerHost(QWidget *parent) : QWidget(parent) @@ -164,9 +62,6 @@ BannerHost::BannerHost(QWidget *parent) : QWidget(parent) connect(&clock, &QTimer::timeout, this, &BannerHost::tick); clock.setInterval(16); // ~60fps; the shader itself is cheap, this is just a wall clock - connect(themeManager, &ThemeManager::themeChanged, this, &BannerHost::applyThemeColors); - applyThemeColors(); - applyMotifPreset(currentMotif); updateAspect(); } @@ -228,9 +123,9 @@ void BannerHost::applyMotifPreset(Motif motif) const Preset p = presetFor(motif); - config->setColorA(bannerColorA); - config->setColorB(bannerColorB); - config->setAccent(bannerAccent); + config->setColorA(QColor(kColorA)); + config->setColorB(QColor(kColorB)); + config->setAccent(QColor(kAccent)); config->setLogoVisible(motif == Motif::Welcome); if (isFirstApply) { @@ -288,34 +183,8 @@ void BannerHost::hideEvent(QHideEvent *event) clock.stop(); } -void BannerHost::applyThemeColors() -{ - const SuggestedColors colors = suggestedBannerColors(); - bannerColorA = colors.colorA; - bannerColorB = colors.colorB; - bannerAccent = colors.accent; - - if (usingFallback) { - fallback->setColors(bannerColorA, bannerColorB); - fallback->update(); - } else if (config) { - config->setColorA(bannerColorA); - config->setColorB(bannerColorB); - config->setAccent(bannerAccent); - config->setGlowColor(colors.glowColor); - config->setBrandStrong(colors.brandStrong); - config->setBrandSoft(colors.brandSoft); - config->setVignetteMin(colors.vignetteMin); - } -} - void BannerHost::tick() { - // Palette previews (e.g. accent drags in the wizard's QuickSetupPanel) - // apply qApp->palette() without firing themeChanged, so re-derive here; - // BannerShaderConfig's setters are equality-guarded, so this is a no-op - // unless the colours actually changed. - applyThemeColors(); if (config) { qreal t = elapsed.elapsed() / 1000.0; config->setTime(t); diff --git a/cockatrice/src/interface/widgets/onboarding/shader_banner_widget.h b/cockatrice/src/interface/widgets/onboarding/shader_banner_widget.h index ac47b1141..2e230ad7f 100644 --- a/cockatrice/src/interface/widgets/onboarding/shader_banner_widget.h +++ b/cockatrice/src/interface/widgets/onboarding/shader_banner_widget.h @@ -1,7 +1,6 @@ #ifndef SHADER_BANNER_WIDGET_H #define SHADER_BANNER_WIDGET_H -#include #include #include #include @@ -54,7 +53,6 @@ protected: private slots: void tick(); void onSceneGraphFailed(); - void applyThemeColors(); private: struct Preset @@ -75,12 +73,6 @@ private: BannerShaderConfig *config = nullptr; GradientFallbackWidget *fallback = nullptr; - // Palette-derived banner colours -- the theme's window hue forced down to - // the banner's curated darkness, plus the theme's Highlight as accent. - QColor bannerColorA; - QColor bannerColorB; - QColor bannerAccent; - QTimer clock; QElapsedTimer elapsed; Motif currentMotif = Motif::Welcome; diff --git a/cockatrice/src/interface/widgets/onboarding/shaders/brand_banner.frag b/cockatrice/src/interface/widgets/onboarding/shaders/brand_banner.frag index 2bf6d0abf..508bd4bc4 100644 --- a/cockatrice/src/interface/widgets/onboarding/shaders/brand_banner.frag +++ b/cockatrice/src/interface/widgets/onboarding/shaders/brand_banner.frag @@ -28,8 +28,6 @@ layout(std140, binding = 0) uniform buf vec4 uColorA; vec4 uColorB; vec4 uAccent; - vec4 uGlowColor; - float uVignetteMin; float uLogoGlow; }; @@ -121,7 +119,7 @@ vec3 backgroundField(vec2 uv, float time) // Accent-coloured fog layer: flowNoise peaks above 0.6 contribute accent float fog = flowNoise(uv * 0.8 + vec2(100.0, 50.0), time * 0.02); - col += uAccent.rgb * max(fog - 0.6, 0.0) * 0.14; + col += uAccent.rgb * max(fog - 0.6, 0.0) * 0.10; return col; } @@ -138,17 +136,14 @@ vec3 motifWelcome(vec2 uv, vec3 bg, float t) vec2 center = vec2(asp * 0.5, 0.5); float cDist = length(ac - center); - // Centre bloom at logo position; intensity scales with uLogoGlow. The - // QML brandGlow halo now supplies the primary logo surround (the two - // brand appColors), so this shader bloom is deliberately kept as a subtle - // ambience rather than a competing glow. + // Centre bloom at logo position; intensity scales with uLogoGlow float centreLight = bloom(cDist, 0.08 * asp, 0.40 * asp); - col += uGlowColor.rgb * centreLight * 0.20 * uLogoGlow; + col += centreLight * 0.20 * uLogoGlow; // Flow-noise shimmer gated by Gaussian mask at centre float shimmer = flowNoise(ac * 0.8 + vec2(55.0, 33.0), t * 0.05) * 0.5 + 0.5; float shimmerMask = exp(-(cDist * cDist) / (0.18 * asp * 0.18 * asp)); - col += uGlowColor.rgb * shimmer * shimmerMask * 0.04 * uLogoGlow; + col += shimmer * shimmerMask * 0.04 * uLogoGlow; // 48 ember particles: hash-seeded position, speed, size, brightness. // Embers within a distance threshold of centre are deflected into an @@ -167,8 +162,8 @@ vec3 motifWelcome(vec2 uv, vec3 bg, float t) float pX = baseX * asp + sin(t * driftFreq + fi * 1.7) * driftAmp * asp; float pY = fract(baseY + t * riseSpeed); - float size = 0.010 + hash21(vec2(fi * 2.9, uSeed * 4.7)) * 0.014; - float bright = 0.18 + hash21(vec2(fi * 6.1, uSeed * 0.9)) * 0.32; + float size = 0.006 + hash21(vec2(fi * 2.9, uSeed * 4.7)) * 0.012; + float bright = 0.15 + hash21(vec2(fi * 6.1, uSeed * 0.9)) * 0.30; // Fade out near top/bottom edges float edgeFade = smoothstep(0.0, 0.12, pY) * smoothstep(1.0, 0.88, pY); @@ -237,7 +232,7 @@ vec3 motifCardDatabase(vec2 uv, vec3 bg, float t) // Semi-transparent dark fill float fill = smoothstep(0.015, -0.005, d); - col = mix(col, uColorB.rgb * 0.60, fill * 0.62); + col = mix(col, uColorB.rgb * 0.55, fill * 0.50); // Accent outline float edge = smoothstep(0.035, 0.0, abs(d)); @@ -314,7 +309,7 @@ vec3 motifAccount(vec2 uv, vec3 bg, float t) // Node glow via bloom; intensity modulated by pulse float dist = length(ac - pos); - col += uAccent.rgb * bloom(dist, 0.018, 0.08) * mix(0.30, 0.55, pulse); + col += uAccent.rgb * bloom(dist, 0.018, 0.08) * mix(0.20, 0.45, pulse); } // Edges: connect nodes within a radius threshold @@ -328,19 +323,19 @@ vec3 motifAccount(vec2 uv, vec3 bg, float t) vec2 ba = nodePos[j] - nodePos[i]; float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); float lineDist = length(pa - ba * h); - col += uAccent.rgb * smoothstep(0.010, 0.0, lineDist) * strength * 0.14; + col += uAccent.rgb * smoothstep(0.010, 0.0, lineDist) * strength * 0.10; } } } // Central bloom at banner centre float cDist = length(ac - center); - col += uAccent.rgb * bloom(cDist, 0.04, 0.25) * 0.20; + col += uAccent.rgb * bloom(cDist, 0.04, 0.25) * 0.12; // Periodic expanding ring from centre float ripplePhase = t * 0.4; float rippleDist = abs(cDist - fract(ripplePhase) * asp * 0.7); - col += uAccent.rgb * smoothstep(0.02, 0.0, rippleDist) * 0.14; + col += uAccent.rgb * smoothstep(0.02, 0.0, rippleDist) * 0.10; return col; } @@ -461,8 +456,6 @@ void main() else if (uMode < 4.5) col = motifPreferences(uv, bg, t); else col = motifFinish(uv, bg, t); - // Corner vignette; uVignetteMin is scheme-driven (0.62 on dark stages, - // gentler on light ones so near-white corners don't go muddy grey). - col *= mix(uVignetteMin, 1.0, vignette(uv)); + col *= mix(0.62, 1.0, vignette(uv)); fragColor = vec4(col, 1.0) * qt_Opacity; } diff --git a/cockatrice/src/interface/widgets/onboarding/shaders/brand_plate.frag b/cockatrice/src/interface/widgets/onboarding/shaders/brand_plate.frag deleted file mode 100644 index 43b798c9f..000000000 --- a/cockatrice/src/interface/widgets/onboarding/shaders/brand_plate.frag +++ /dev/null @@ -1,42 +0,0 @@ -#version 440 - -// The logo's gradient plate, drawn by us rather than the baked-in SVG: a -// linear blend between the two brand appColors (light AccentSoft at the -// top-left grading to dark AccentStrong at the bottom-right, mirroring -// cockatrice.svg's linearGradient4265-7-8 userSpaceOnUse axis), clipped to -// the bird's full silhouette via the full-color logo's alpha (uSilhouette). -// The white highlight path (cockatrice-logo-white) is overlaid in QML on top, -// exactly as the SVG stacks its white path over the gradient paths. Fully -// static: no glow, no breathing — the plate just sits there like the home -// widget's QPainter composite. - -layout(location = 0) in vec2 qt_TexCoord0; -layout(location = 0) out vec4 fragColor; - -layout(std140, binding = 0) uniform buf -{ - mat4 qt_Matrix; - float qt_Opacity; - vec4 uStrong; - vec4 uSoft; -}; - -// The full-color logo's alpha channel acts as the silhouette mask: the -// gradient only appears inside the bird, exactly like the SVG's gradient paths. -layout(binding = 1) uniform sampler2D uSilhouette; - -void main() -{ - // Recreate cockatrice.svg's own gradient geometry (linearGradient4265-7-8, - // userSpaceOnUse): light AccentSoft at the start point S=(-8.097,-97.746), - // dark AccentStrong at the end E=(162.455,295.208), on the SVG's 300x300 - // canvas. Normalized to UV space, V=E-S=(0.5685,1.3098), so - // t = dot(uv - S_norm, V)/|V|^2 with S_norm=(-0.0270,-0.3258). - float t = clamp(dot(qt_TexCoord0 - vec2(-0.02699, -0.32582), vec2(0.56851, 1.30985)) / 2.03891, 0.0, 1.0); - vec3 color = mix(uSoft.rgb, uStrong.rgb, t); - - // Anti-aliased silhouette clip from the full-color logo's alpha. - float alpha = texture(uSilhouette, qt_TexCoord0).a; - - fragColor = vec4(color * alpha, alpha) * qt_Opacity; -} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp index e8816847e..0b77ca185 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp @@ -1,19 +1,13 @@ #include "printing_selector_card_overlay_widget.h" #include "../../../client/settings/cache_settings.h" -#include "../../card_picture_loader/card_picture_loader.h" #include "../cards/card_info_picture_widget.h" #include "printing_selector_card_display_widget.h" -#include -#include #include #include #include -#include #include -#include -#include #include #include #include @@ -21,49 +15,6 @@ #include #include -namespace -{ -/** - * @brief Places the preview beside the highlighted action inside the given screen. - * - * Side-aware: hugs the side of the action that has room, aligned with its row, then - * clamps every edge so the preview always lands fully on-screen on first show. - */ -QPoint -previewPositionNear(const QRect &actionRect, const QSize &labelSize, const QRect &screenGeometry, int previewOffset) -{ - const bool rightFits = actionRect.right() + previewOffset + labelSize.width() <= screenGeometry.right(); - const bool leftFits = actionRect.left() - previewOffset - labelSize.width() >= screenGeometry.left(); - - int x; - if (rightFits) { - x = actionRect.right() + previewOffset; - } else if (leftFits) { - x = actionRect.left() - previewOffset - labelSize.width(); - } else { - x = actionRect.left(); - } - x = qMax(screenGeometry.left(), x); - x = qMin(screenGeometry.right() - labelSize.width() + 1, x); - - const bool belowFits = actionRect.bottom() + previewOffset + labelSize.height() <= screenGeometry.bottom(); - const bool aboveFits = actionRect.top() - previewOffset - labelSize.height() >= screenGeometry.top(); - - int y; - if (belowFits) { - y = actionRect.bottom() + previewOffset; - } else if (aboveFits) { - y = actionRect.top() - previewOffset - labelSize.height(); - } else { - y = actionRect.top(); - } - y = qMax(screenGeometry.top(), y); - y = qMin(screenGeometry.bottom() - labelSize.height() + 1, y); - - return {x, y}; -} -} // namespace - /** * @brief Constructs a PrintingSelectorCardOverlayWidget for displaying a card overlay. * @@ -99,31 +50,6 @@ PrintingSelectorCardOverlayWidget::PrintingSelectorCardOverlayWidget(QWidget *pa initializePinBadge(); - // Parent the preview to this overlay so it is destroyed with it (Qt::ToolTip keeps - // it a frameless, non-activating top-level window despite the parent). - cardOverridePreviewLabel = new QLabel(this, Qt::ToolTip); - cardOverridePreviewLabel->setWindowFlag(Qt::FramelessWindowHint); - cardOverridePreviewLabel->setAttribute(Qt::WA_ShowWithoutActivating); - cardOverridePreviewLabel->setScaledContents(true); - cardOverridePreviewLabel->hide(); - - // While the preview is visible, keep it honest: when the hovered printing's art - // resolves (all alternate printings share the root card's CardInfo), redraw it in place. - if (rootCard.getCardPtr()) { - connect(rootCard.getCardPtr().data(), &CardInfo::pixmapUpdated, this, [this] { - if (cardOverridePreviewLabel->isVisible()) { - refreshPreview(); - } - }); - } - - // Alt-Tab / app-inactive must not strand the floating preview. - connect(qApp, &QGuiApplication::applicationStateChanged, this, [this](Qt::ApplicationState state) { - if (state != Qt::ApplicationActive) { - hidePreview(); - } - }); - // Update when this overlay emits cardPreferenceChanged or when size/scale changes connect(this, &PrintingSelectorCardOverlayWidget::cardPreferenceChanged, this, &PrintingSelectorCardOverlayWidget::updatePinBadgeVisibility); @@ -259,23 +185,19 @@ void PrintingSelectorCardOverlayWidget::leaveEvent(QEvent *event) } /** - * @brief Creates and shows the card-overlay context menu. + * @brief Creates and shows a custom context menu when the right mouse button is clicked. * - * The menu includes the card art preference (Pin/Unpin Printing), the Image Overrides - * submenu (Load Custom Image, Clear Custom Image, and one entry per alternate printing with a - * live preview), and the Show Related cards submenu. + * The context menu includes an option to show related cards, which displays a submenu with actions + * for each related card. When an action is triggered, the card information is updated, and the + * printing selector is shown. * - * @param point The local position the menu should pop at. + * @param point The position of the mouse when the right-click occurred. */ void PrintingSelectorCardOverlayWidget::customMenu(QPoint point) { QMenu menu; - hidePreview(); // Clear any preview state left over from a previous menu run. - - // Submenus are owned by the stack-allocated top-level menu (addMenu() does not - // transfer ownership). - auto *preferenceMenu = new QMenu(tr("Preference"), &menu); + auto *preferenceMenu = new QMenu(tr("Preference")); menu.addMenu(preferenceMenu); const auto &preferredProviderId = @@ -297,66 +219,8 @@ void PrintingSelectorCardOverlayWidget::customMenu(QPoint point) }); } - menu.addSeparator(); - - auto *overrideMenu = new QMenu(tr("Image Overrides"), &menu); - - auto *loadCustomAction = overrideMenu->addAction(tr("Load Custom Image...")); - auto *clearOverrideAction = overrideMenu->addAction(tr("Clear Custom Image")); - - // Nothing to clear on a card that has no local override yet. - clearOverrideAction->setEnabled(CardPictureLoader::hasLocalOverrides(rootCard)); - - overrideMenu->addSeparator(); - - const auto &allSets = rootCard.getInfo().getSets(); - - for (const auto &set : allSets) { - for (const auto &printing : set) { - if (printing == rootCard.getPrinting()) { - continue; - } - - // The submenu is already scoped to this card, so the rows lead with set + - // collector; only printings with a distinct display name add their own name. - const CardSetPtr cardSet = printing.getSet(); - if (!cardSet) { - continue; - } - - QString label = tr("%1 %2").arg(cardSet->getCorrectedShortName(), printing.getProperty("num")); - - auto *action = overrideMenu->addAction(label); - - ExactCard overrideCard(rootCard.getCardPtr(), printing); - action->setData(QVariant::fromValue(overrideCard)); - - connect(action, &QAction::triggered, this, [this, overrideCard]() { - CardPictureLoader::getInstance().installPrintingOverride(rootCard, overrideCard); - QPixmapCache::clear(); - rootCard.emitPixmapUpdated(); // refresh the overlay art in place, like the other paths - }); - } - } - - connect(clearOverrideAction, &QAction::triggered, this, [this]() { - CardPictureLoader::deleteAllLocalOverrides(rootCard); - QPixmapCache::clear(); - rootCard.emitPixmapUpdated(); // force UI refresh - }); - - connect(loadCustomAction, &QAction::triggered, this, &PrintingSelectorCardOverlayWidget::loadCustomImage); - - connect(overrideMenu, &QMenu::hovered, this, &PrintingSelectorCardOverlayWidget::showPreviewForAction); - connect(overrideMenu, &QMenu::aboutToHide, this, &PrintingSelectorCardOverlayWidget::hidePreview); - connect(overrideMenu, &QMenu::triggered, this, &PrintingSelectorCardOverlayWidget::hidePreview); - - menu.addMenu(overrideMenu); - - menu.addSeparator(); - // filling out the related cards submenu - auto *relatedMenu = new QMenu(tr("Show Related cards"), &menu); + auto *relatedMenu = new QMenu(tr("Show Related cards")); menu.addMenu(relatedMenu); auto relatedCards = rootCard.getInfo().getAllRelatedCards(); if (relatedCards.isEmpty()) { @@ -371,11 +235,7 @@ void PrintingSelectorCardOverlayWidget::customMenu(QPoint point) }); } } - // The preview anchors itself to this popup's global geometry while it is open, so the - // pointer must stay valid for the whole exec() and be dropped before the stack unwinds. - previewSourceMenu = overrideMenu; menu.exec(this->mapToGlobal(point)); - previewSourceMenu = nullptr; } /** @@ -431,136 +291,3 @@ void PrintingSelectorCardOverlayWidget::initializePinBadge() pinBadge->setVisible(false); pinBadge->raise(); } - -/** - * @brief Asks for an image file and installs it as the card's custom art. - * - * Unreadable files answer with a visible warning instead of a silent no-op. - */ -void PrintingSelectorCardOverlayWidget::loadCustomImage() -{ - QString filePath = QFileDialog::getOpenFileName(this, tr("Select Card Image"), QString(), - tr("Images (*.png *.jpg *.jpeg *.webp)")); - - if (filePath.isEmpty()) { - return; - } - - QPixmap pixmap(filePath); - if (pixmap.isNull()) { - // No silent paths: a file that cannot be read answers visibly instead of a no-op. - QMessageBox::warning(this, tr("Load Custom Image"), tr("The selected file could not be read as an image.")); - return; - } - - CardPictureLoader::getInstance().saveCardImageToLocalStorage(rootCard, pixmap, true); - - QPixmapCache::clear(); - rootCard.emitPixmapUpdated(); -} - -/** - * @brief Shows the hover preview for a highlighted printing entry in the Image Overrides submenu. - * - * QMenu::hovered fires on keyboard highlight too, so the preview appears when arrows walk - * onto a printing entry, not only under the mouse. - * - * Non-printing entries (e.g., Load Custom Image, Clear Custom Image) hide the preview. - * - * @param action The action that was highlighted. - */ -void PrintingSelectorCardOverlayWidget::showPreviewForAction(QAction *action) -{ - if (!action) { - hidePreview(); - return; - } - - const QVariant data = action->data(); - - if (!data.canConvert()) { - hidePreview(); - return; - } - - const ExactCard previewCard = qvariant_cast(data); - if (previewCard.isEmpty()) { - hidePreview(); - return; - } - - hoveredOverrideCard = previewCard; - hoveredOverrideAction = action; - refreshPreview(); -} - -/** - * @brief Renders the hover preview for the currently highlighted printing. - * - * The preview shows the loading placeholder while the art is pending and swaps in the real - * art when it resolves. The label is positioned against its already-resized geometry so the - * first-ever show at the screen's edges stays fully on-screen. - */ -void PrintingSelectorCardOverlayWidget::refreshPreview() -{ - if (hoveredOverrideCard.isEmpty()) { - hidePreview(); - return; - } - - constexpr QSize previewSize(240, 336); - constexpr int previewOffset = 20; - - QPixmap pixmap; - CardPictureLoader::getPixmap(pixmap, hoveredOverrideCard, previewSize); - - if (pixmap.isNull()) { - // Keep the preview honest while loading: show the loading placeholder instead of a void. - // Fetch at the logical size and let the label scale it, so the placeholder matches the - // real art's footprint rather than doubling on HiDPI displays. - CardPictureLoader::getCardBackLoadingInProgressPixmap(pixmap, previewSize); - } - - cardOverridePreviewLabel->setPixmap(pixmap); - // QPixmap::size() is physical pixels; the label layout must use the device-independent size - // so the preview keeps a constant footprint across DPI settings (QScreen geometry is logical). - const QSize labelSize = pixmap.deviceIndependentSize().toSize(); - cardOverridePreviewLabel->resize(labelSize); - - // Anchor the preview to the walked submenu popup rather than QCursor::pos(), which is idle - // under keyboard-only operation: a keyboard-highlighted row must preview at the same place as - // a hovered one. The mouse path is unchanged in effect — the popup sits under the cursor, so - // the preview stays beside the row in both modalities. - const QMenu *popup = previewSourceMenu; - if (!popup || !popup->isVisible() || !hoveredOverrideAction) { - hidePreview(); - return; - } - - const QRect popupGeometry = popup->geometry(); - const QRect actionRectLocal = popup->actionGeometry(hoveredOverrideAction); - const QRect actionRect(popupGeometry.topLeft() + actionRectLocal.topLeft(), actionRectLocal.size()); - - QScreen *screen = QGuiApplication::screenAt(popupGeometry.center()); - if (!screen) { - hidePreview(); - return; - } - const QRect &screenGeometry = screen->geometry(); - - cardOverridePreviewLabel->move(previewPositionNear(actionRect, labelSize, screenGeometry, previewOffset)); - cardOverridePreviewLabel->show(); -} - -/** - * @brief Hides the hover preview and forgets the currently highlighted printing. - */ -void PrintingSelectorCardOverlayWidget::hidePreview() -{ - hoveredOverrideCard = ExactCard(); - hoveredOverrideAction = nullptr; - - if (cardOverridePreviewLabel) { - cardOverridePreviewLabel->hide(); - } -} diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.h index fbcfa9230..228393c9c 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.h @@ -13,9 +13,6 @@ #include -class QAction; -class QMenu; - class PrintingSelectorCardOverlayWidget : public QWidget { Q_OBJECT @@ -46,19 +43,11 @@ private slots: private: void initializePinBadge(); - void loadCustomImage(); - void showPreviewForAction(QAction *action); - void refreshPreview(); - void hidePreview(); CardInfoPictureWidget *cardInfoPicture; AllZonesCardAmountWidget *allZonesCardAmountWidget; QLabel *pinBadge = nullptr; AbstractTabDeckEditor *deckEditor; ExactCard rootCard; - QLabel *cardOverridePreviewLabel = nullptr; - ExactCard hoveredOverrideCard; - QMenu *previewSourceMenu = nullptr; - QAction *hoveredOverrideAction = nullptr; }; #endif // PRINTING_SELECTOR_CARD_OVERLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/server/game_selector.cpp b/cockatrice/src/interface/widgets/server/game_selector.cpp index 659325987..a8bf54e91 100644 --- a/cockatrice/src/interface/widgets/server/game_selector.cpp +++ b/cockatrice/src/interface/widgets/server/game_selector.cpp @@ -369,7 +369,7 @@ void GameSelector::joinGame(const ServerInfo_Game &game, const bool asSpectator, return; } - bool overrideRestrictions = tabSupervisor->canOverrideGameRestrictions(); + bool overrideRestrictions = !tabSupervisor->getAdminLocked(); // Joining a full game without override privileges silently becomes a // spectator join, so ask first instead of surprising the player. @@ -462,7 +462,7 @@ void GameSelector::enableButtonsForIndex(const QModelIndex ¤t) } const ServerInfo_Game &game = gameListModel->getGame(current.data(Qt::UserRole).toInt()); - bool overrideRestrictions = tabSupervisor->canOverrideGameRestrictions(); + bool overrideRestrictions = !tabSupervisor->getAdminLocked(); spectateButton->setEnabled(game.spectators_allowed() || overrideRestrictions); joinButton->setEnabled(game.player_count() < game.max_players() || overrideRestrictions); diff --git a/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.cpp index f161f0f19..c8494f095 100644 --- a/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.cpp +++ b/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.cpp @@ -59,8 +59,8 @@ AppearanceSettingsPage::AppearanceSettingsPage() connect(&schemeCombo, &QComboBox::currentIndexChanged, this, [this] { themeManager->setColorScheme(schemeCombo.currentData().toString()); }); - // Qt widget style; "System" lets the application decide - styleCombo.addItem(tr("System"), QStringLiteral("System")); + // Qt widget style; "Default" lets the application decide + styleCombo.addItem(tr("Default"), QStringLiteral("Default")); for (const QString &key : QStyleFactory::keys()) { styleCombo.addItem(key, key); } @@ -132,10 +132,6 @@ AppearanceSettingsPage::AppearanceSettingsPage() connect(&homeTabDisplayCardNameCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings.appearance(), &AppearanceSettings::setHomeTabDisplayCardName); - homeTabBackgroundDimCheckBox.setChecked(settings.appearance().getHomeTabBackgroundDim()); - connect(&homeTabBackgroundDimCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings.appearance(), - &AppearanceSettings::setHomeTabBackgroundDim); - for (const auto &entry : HomeTabButtonColor::all()) { homeTabButtonColorSourceBox.addItem(QObject::tr(entry.trKey)); } @@ -154,7 +150,6 @@ AppearanceSettingsPage::AppearanceSettingsPage() homeTabGrid->addWidget(&homeTabDisplayCardNameCheckBox, 2, 0, 1, 2); homeTabGrid->addWidget(&homeTabButtonColorSourceLabel, 3, 0); homeTabGrid->addWidget(&homeTabButtonColorSourceBox, 3, 1); - homeTabGrid->addWidget(&homeTabBackgroundDimCheckBox, 4, 0, 1, 2); homeTabGroupBox = new QGroupBox; homeTabGroupBox->setLayout(homeTabGrid); @@ -513,12 +508,9 @@ void AppearanceSettingsPage::retranslateUi() homeTabBackgroundShuffleFrequencyLabel.setText(tr("Home tab background shuffle frequency:")); homeTabBackgroundShuffleFrequencySpinBox.setSpecialValueText(tr("Disabled")); homeTabDisplayCardNameCheckBox.setText(tr("Display card name of background in bottom right")); - homeTabBackgroundDimCheckBox.setText(tr("Dim the home tab background")); - homeTabBackgroundDimCheckBox.setToolTip( - tr("Draw a translucent overlay over the home tab background so buttons and text stand out")); homeTabButtonColorSourceLabel.setText(tr("Home tab button color:")); homeTabButtonColorSourceBox.setToolTip( - tr("Use the theme's identity accent colors, or extract colors from the background image")); + tr("Automatic: extract from background if present, otherwise use theme default")); playmatGroupBox->setTitle(tr("Playmat settings")); playmatVisibilityLabel.setText(tr("Playmat visibility:")); diff --git a/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.h b/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.h index bec4cd72f..8db71ff8f 100644 --- a/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.h +++ b/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.h @@ -41,7 +41,6 @@ private: QLabel homeTabBackgroundShuffleFrequencyLabel; QSpinBox homeTabBackgroundShuffleFrequencySpinBox; QCheckBox homeTabDisplayCardNameCheckBox; - QCheckBox homeTabBackgroundDimCheckBox; QLabel homeTabButtonColorSourceLabel; QComboBox homeTabButtonColorSourceBox; diff --git a/cockatrice/src/interface/widgets/settings_page/general_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/general_settings_page.cpp index 36436f8a3..62b06fb60 100644 --- a/cockatrice/src/interface/widgets/settings_page/general_settings_page.cpp +++ b/cockatrice/src/interface/widgets/settings_page/general_settings_page.cpp @@ -1,21 +1,15 @@ #include "general_settings_page.h" #include "../../../client/settings/cache_settings.h" -#include "../interface/card_picture_loader/card_picture_loader.h" #include "../main.h" #include "../server/user/user_info_connection.h" #include "update/client/release_channel.h" #include -#include #include #include #include -#include #include -#include -#include -#include #include #include #include @@ -52,27 +46,10 @@ GeneralSettingsPage::GeneralSettingsPage() connect(&languageBox, qOverload(&QComboBox::currentIndexChanged), this, &GeneralSettingsPage::languageBoxChanged); - // card text & images language, independent of the UI language - cardLanguageBox.addItem(tr("English"), "en"); - for (const QString &code : CardLocalization::supportedLanguages()) { - cardLanguageBox.addItem(CardLocalization::languageDisplayName(code), code); - } - const int cardLangIndex = cardLanguageBox.findData(SettingsCache::instance().cardsDisplay().getCardLang()); - cardLanguageBox.setCurrentIndex(cardLangIndex < 0 ? 0 : cardLangIndex); - - connect(&cardLanguageBox, qOverload(&QComboBox::currentIndexChanged), this, - &GeneralSettingsPage::cardLanguageBoxChanged); - auto *languageGrid = new QGridLayout; languageGrid->addWidget(&languageLabel, 0, 0); languageGrid->addWidget(&languageBox, 0, 1); - languageGrid->addWidget(&cardLanguageLabel, 1, 0); - languageGrid->addWidget(&cardLanguageBox, 1, 1); - languageGrid->addWidget(&cardLanguageNoteLabel, 2, 1); - languageGrid->addWidget(&advertiseTranslationPageLabel, 3, 1, Qt::AlignRight); - - cardLanguageNoteLabel.setWordWrap(true); - cardLanguageNoteLabel.setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + languageGrid->addWidget(&advertiseTranslationPageLabel, 1, 1, Qt::AlignRight); languageGroupBox = new QGroupBox; languageGroupBox->setLayout(languageGrid); @@ -435,52 +412,6 @@ void GeneralSettingsPage::languageBoxChanged(int index) SettingsCache::instance().personal().setLang(languageBox.itemData(index).toString()); } -void GeneralSettingsPage::cardLanguageBoxChanged(int index) -{ - const QString lang = cardLanguageBox.itemData(index).toString(); - SettingsCache::instance().cardsDisplay().setCardLang(lang); - - // Switching to a non-default language only takes effect after the card - // database is re-imported with that language selected; English data is always - // present, so switching back to English needs no prompt. - if (lang == "en") { - return; - } - - // The binary cache does not track the language its entries were imported in, - // and the downloaded pictures were fetched with English art names, so both are - // stale until Oracle re-imports the database in the new language: drop them. - QFile::remove(SettingsCache::instance().getCardDatabasePath() + ".cache"); - CardPictureLoader::clearNetworkCache(); - CardPictureLoader::clearPixmapCache(); - - // Art is resolved by the translated card name for non-English languages, so the - // matching Scryfall URL is added to the top of the download list. It stays - // visible in the deck editor settings, where it can be removed or reordered. - const bool localizedUrlAdded = SettingsCache::instance().downloads().addLocalizedScryfallUrl(); - - QString message = tr("

The card database only contains English card data. To see cards in %1, " - "Oracle must run once with this language selected and re-import the card " - "database.

" - "

The cached database and the downloaded card pictures have been cleared, so a " - "re-import is picked up without stale entries.

") - .arg(cardLanguageBox.itemText(index)); - if (localizedUrlAdded) { - message += tr("

The Scryfall URL that resolves card art by translated name was added to the top of your " - "download list. You can remove or reorder it any time.

"); - } - message += tr("

Run Oracle now?

"); - - const QMessageBox::StandardButton answer = QMessageBox::question( - this, tr("Card text & images language changed"), message, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); - - // The answer only controls whether Oracle starts right away; the caches stay - // cleared so the next import or launch rebuilds them in the new language. - if (answer == QMessageBox::Yes) { - emit cardDatabaseUpdateRequested(); - } -} - void GeneralSettingsPage::updateStartupServerControlsVisibility() { const int index = startupTabSelector.currentIndex(); @@ -498,10 +429,6 @@ void GeneralSettingsPage::retranslateUi() languageGroupBox->setTitle(tr("Language settings")); languageLabel.setText(tr("Language:")); - cardLanguageBox.setItemText(0, tr("English")); - cardLanguageLabel.setText(tr("Card text & images language:")); - cardLanguageNoteLabel.setText( - tr("Foreign card names, text and art apply after you update the card database (Oracle).")); advertiseTranslationPageLabel.setText( QString("%2").arg(WIKI_TRANSLATION_FAQ).arg(tr("How to help with translations"))); diff --git a/cockatrice/src/interface/widgets/settings_page/general_settings_page.h b/cockatrice/src/interface/widgets/settings_page/general_settings_page.h index 7afba158f..e0c1a47bf 100644 --- a/cockatrice/src/interface/widgets/settings_page/general_settings_page.h +++ b/cockatrice/src/interface/widgets/settings_page/general_settings_page.h @@ -23,10 +23,6 @@ public: static QStringList findQmFiles(); static QString languageName(const QString &lang); -signals: - /// Request to re-import the card database with the newly selected card language - void cardDatabaseUpdateRequested(); - private slots: void deckPathButtonClicked(); void filtersPathButtonClicked(); @@ -37,7 +33,6 @@ private slots: void tokenDatabasePathButtonClicked(); void resetAllPathsClicked(); void languageBoxChanged(int index); - void cardLanguageBoxChanged(int index); void updateStartupServerControlsVisibility(); private: @@ -51,10 +46,6 @@ private: QComboBox languageBox; QLabel advertiseTranslationPageLabel; - QLabel cardLanguageLabel; - QComboBox cardLanguageBox; - QLabel cardLanguageNoteLabel; - QLabel updateReleaseChannelLabel; QComboBox updateReleaseChannelBox; QCheckBox startupUpdateCheckCheckBox; diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp index 657ef3dbe..66b68d823 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp @@ -1,6 +1,5 @@ #include "archidekt_api_response_deck_display_widget.h" -#include "../../../../../../client/settings/cache_settings.h" #include "../../../../../deck_loader/card_node_function.h" #include "../../../../../deck_loader/deck_loader.h" #include "../../../../cards/card_size_widget.h" @@ -11,7 +10,6 @@ #include #include -#include ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWidget *parent, ArchidektApiResponseDeck _response, @@ -122,9 +120,6 @@ ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWi } model = new DeckListModel(this); - model->setDisplayLanguage(SettingsCache::instance().cardsDisplay().getCardLang()); - connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardLangChanged, model, - [this](const QString &lang) { model->setDisplayLanguage(lang); }); connect(model, &DeckListModel::modelReset, this, &ArchidektApiResponseDeckDisplayWidget::decklistModelReset); auto decklist = QSharedPointer(new DeckList); diff --git a/cockatrice/src/interface/widgets/tabs/tab_server.cpp b/cockatrice/src/interface/widgets/tabs/tab_server.cpp index fca32094c..13a77e957 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_server.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_server.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include @@ -186,37 +185,25 @@ void TabServer::processServerMessageEvent(const Event_ServerMessage &event) void TabServer::joinRoom(int id, bool setCurrent) { TabRoom *room = tabSupervisor->getRoomTabs().value(id); - if (room) { - if (setCurrent) { - tabSupervisor->setCurrentWidget((QWidget *)room); - } + if (!room) { + Command_JoinRoom cmd; + cmd.set_room_id(id); + + PendingCommand *pend = client->prepareSessionCommand(cmd); + pend->setExtraData(setCurrent); + connect(pend, &PendingCommand::finished, this, + [this, id](const Response &r, const CommandContainer &c, const QVariant &v) { + joinRoomFinished(r, c, v, id); + }); + + client->sendCommand(pend); + return; } - auto pendingIt = pendingRoomJoins.find(id); - if (pendingIt != pendingRoomJoins.end()) { - // A join for this room is already in flight: the room tab opens when its response - // arrives. Fold the new request into the pending one so that, for example, clicking - // a room the selector is auto-joining does not send a second Command_JoinRoom - the - // server would reject that duplicate with RespContextError. - if (setCurrent) { - pendingIt.value() = true; - } - return; + if (setCurrent) { + tabSupervisor->setCurrentWidget((QWidget *)room); } - - pendingRoomJoins.insert(id, setCurrent); - - Command_JoinRoom cmd; - cmd.set_room_id(id); - - PendingCommand *pend = client->prepareSessionCommand(cmd); - pend->setExtraData(setCurrent); - connect( - pend, &PendingCommand::finished, this, - [this, id](const Response &r, const CommandContainer &c, const QVariant &v) { joinRoomFinished(r, c, v, id); }); - - client->sendCommand(pend); } void TabServer::joinRoomFinished(const Response &r, @@ -224,72 +211,34 @@ void TabServer::joinRoomFinished(const Response &r, const QVariant &extraData, int roomId) { - const bool setCurrent = pendingRoomJoins.value(roomId, extraData.toBool()); - pendingRoomJoins.remove(roomId); - const bool healedJoin = healedRoomJoins.contains(roomId); - healedRoomJoins.remove(roomId); - switch (r.response_code()) { case Response::RespOk: break; case Response::RespNameNotFound: - if (setCurrent) { - QMessageBox::critical(this, tr("Error"), - tr("Failed to join the server room: it doesn't exist on the server.")); - } + QMessageBox::critical(this, tr("Error"), + tr("Failed to join the server room: it doesn't exist on the server.")); emit roomJoinFailed(roomId); return; case Response::RespContextError: - if (healedJoin) { - // The rejoin below was already answered and the server still rejects the join, so - // the stale-membership heal cannot help: surface the error. The guard was already - // released above so a later user-initiated join may try a fresh heal. - if (setCurrent) { - QMessageBox::critical( - this, tr("Error"), - tr("The server thinks you are in the server room but your client is unable to display it. " - "Try restarting your client.")); - } - emit roomJoinFailed(roomId); - return; - } - // The server already had us registered in the room even though no tab was open, - // usually because two join attempts for the same room overlapped. Leaving and - // rejoining makes the server reply with a fresh RespOk so the tab is displayed - // without requiring a client restart. The guard above covers exactly the rejoin that - // leaveAndRejoinRoom triggers, so a server that keeps replying with RespContextError - // gets one heal attempt per join instead of an endless recursion. - healedRoomJoins.insert(roomId); - leaveAndRejoinRoom(roomId, setCurrent); + QMessageBox::critical( + this, tr("Error"), + tr("The server thinks you are in the server room but your client is unable to display it. " + "Try restarting your client.")); + emit roomJoinFailed(roomId); return; case Response::RespUserLevelTooLow: - if (setCurrent) { - QMessageBox::critical(this, tr("Error"), - tr("You do not have the required permission to join this server room.")); - } + QMessageBox::critical(this, tr("Error"), + tr("You do not have the required permission to join this server room.")); emit roomJoinFailed(roomId); return; default: - if (setCurrent) { - QMessageBox::critical( - this, tr("Error"), - tr("Failed to join the server room due to an unknown error: %1.").arg(r.response_code())); - } + QMessageBox::critical( + this, tr("Error"), + tr("Failed to join the server room due to an unknown error: %1.").arg(r.response_code())); emit roomJoinFailed(roomId); return; } const Response_JoinRoom &resp = r.GetExtension(Response_JoinRoom::ext); - emit roomJoined(resp.room_info(), setCurrent); -} - -void TabServer::leaveAndRejoinRoom(int roomId, bool setCurrent) -{ - // Clear the stale room membership server-side. The leave is sent before the rejoin below, - // so the server no longer considers us a member by the time the join arrives. The leave - // response is intentionally not awaited: commands are processed in send order on the - // connection, and a failed leave (RespNotInRoom) only means the membership was already gone. - client->sendCommand(client->prepareRoomCommand(Command_LeaveRoom(), roomId)); - - joinRoom(roomId, setCurrent); + emit roomJoined(resp.room_info(), extraData.toBool()); } diff --git a/cockatrice/src/interface/widgets/tabs/tab_server.h b/cockatrice/src/interface/widgets/tabs/tab_server.h index 121ff814d..c10b7945b 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_server.h +++ b/cockatrice/src/interface/widgets/tabs/tab_server.h @@ -10,8 +10,6 @@ #include "tab.h" #include -#include -#include #include #include @@ -60,17 +58,10 @@ private slots: int roomId); private: - void leaveAndRejoinRoom(int roomId, bool setCurrent); - AbstractClient *client; RoomSelector *roomSelector; QTextBrowser *serverInfoBox; bool shouldEmitUpdate = false; - /** Room ids with a join command in flight, mapped to whether the tab should be focused once it opens. */ - QHash pendingRoomJoins; - /** Room ids for which a stale-membership heal (leave + rejoin) is currently in flight. Released as soon as the - * rejoin has been answered, so a heal is attempted at most once per join. */ - QSet healedRoomJoins; public: TabServer(TabSupervisor *_tabSupervisor, AbstractClient *_client); diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp index ccb687ff3..462aa420b 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp @@ -1451,11 +1451,6 @@ bool TabSupervisor::getAdminLocked() const return tabAdmin->getLocked(); } -bool TabSupervisor::canOverrideGameRestrictions() const -{ - return !getAdminLocked() || (userInfo->user_level() & ServerInfo_User::IsJudge); -} - void TabSupervisor::processNotifyUserEvent(const Event_NotifyUser &event) { diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.h b/cockatrice/src/interface/widgets/tabs/tab_supervisor.h index adde7f971..aec1d7418 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.h +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.h @@ -171,7 +171,6 @@ public: [[nodiscard]] QList getGameInviteLinksForRoom(int roomId) const; void sendInviteToUser(const QString &userName, const QString &inviteText); [[nodiscard]] bool getAdminLocked() const; - [[nodiscard]] bool canOverrideGameRestrictions() const; void closeEvent(QCloseEvent *event) override; bool switchToGameTabIfAlreadyExists(const int gameId); static void actShowPopup(const QString &message); diff --git a/cockatrice/src/interface/window_main.cpp b/cockatrice/src/interface/window_main.cpp index 722df90ef..3daaeb8d3 100644 --- a/cockatrice/src/interface/window_main.cpp +++ b/cockatrice/src/interface/window_main.cpp @@ -33,7 +33,6 @@ #include "../interface/widgets/dialogs/dlg_update.h" #include "../interface/widgets/dialogs/dlg_view_log.h" #include "../interface/widgets/onboarding/first_run_wizard.h" -#include "../interface/widgets/settings_page/general_settings_page.h" #include "../interface/widgets/tabs/tab_game.h" #include "../interface/widgets/tabs/tab_server.h" #include "../interface/widgets/tabs/tab_supervisor.h" @@ -247,8 +246,6 @@ void MainWindow::actFullScreen(bool checked) void MainWindow::actSettings() { DlgSettings dlg(this); - auto *generalPage = qobject_cast(dlg.page(DlgSettings::GeneralPage)); - connect(generalPage, &GeneralSettingsPage::cardDatabaseUpdateRequested, this, &MainWindow::actCheckCardUpdates); dlg.exec(); } diff --git a/cockatrice/themes/CMakeLists.txt b/cockatrice/themes/CMakeLists.txt index 70344d95a..577977551 100644 --- a/cockatrice/themes/CMakeLists.txt +++ b/cockatrice/themes/CMakeLists.txt @@ -2,7 +2,7 @@ # # add themes subfolders -set(defthemes Fabric Fusion Leather Plasma VelvetMarble System) +set(defthemes Default Fabric Fusion Leather Plasma VelvetMarble) if(UNIX) if(APPLE) diff --git a/cockatrice/themes/System/palette-default-dark.toml b/cockatrice/themes/Default/palette-default-dark.toml similarity index 96% rename from cockatrice/themes/System/palette-default-dark.toml rename to cockatrice/themes/Default/palette-default-dark.toml index e101935b4..3ee174a2f 100644 --- a/cockatrice/themes/System/palette-default-dark.toml +++ b/cockatrice/themes/Default/palette-default-dark.toml @@ -61,7 +61,3 @@ ToolTipBase = #ffffffdc ToolTipText = #ff000000 PlaceholderText = #6effffff - -[AppColors] -AccentStrong = #ff148c3c -AccentSoft = #ff78c850 diff --git a/cockatrice/themes/System/theme.cfg b/cockatrice/themes/Default/theme.cfg similarity index 73% rename from cockatrice/themes/System/theme.cfg rename to cockatrice/themes/Default/theme.cfg index 4b756a5e8..d2016a238 100644 --- a/cockatrice/themes/System/theme.cfg +++ b/cockatrice/themes/Default/theme.cfg @@ -2,4 +2,4 @@ ColorScheme = Light [Style] -Name = System +Name = Default diff --git a/cockatrice/themes/Fabric/backgrounds/home-dark.png b/cockatrice/themes/Fabric/backgrounds/home-dark.png deleted file mode 100644 index 8d9514626..000000000 Binary files a/cockatrice/themes/Fabric/backgrounds/home-dark.png and /dev/null differ diff --git a/cockatrice/themes/Fabric/backgrounds/home-light.png b/cockatrice/themes/Fabric/backgrounds/home-light.png deleted file mode 100644 index 78f51a686..000000000 Binary files a/cockatrice/themes/Fabric/backgrounds/home-light.png and /dev/null differ diff --git a/cockatrice/themes/Fabric/palette-default-dark.toml b/cockatrice/themes/Fabric/palette-default-dark.toml deleted file mode 100644 index cad18921a..000000000 --- a/cockatrice/themes/Fabric/palette-default-dark.toml +++ /dev/null @@ -1,74 +0,0 @@ -# Fabric — dark identity palette (navy-cloth chrome) -[Palette] -WindowText = #e8ecf2 -Button = #2b374a -Light = #3a4759 -Midlight = #333f50 -Dark = #141a23 -Mid = #252c38 -Text = #e8ecf2 -BrightText = #8ec2ff -ButtonText = #f2f4f8 -Base = #222c3d -Window = #18202e -Shadow = #0b0f15 -Highlight = #4a82e6 -HighlightedText = #ffffff -Link = #7fb0f2 -LinkVisited = #a98ad9 -AlternateBase = #1d2636 -ToolTipBase = #2b3546 -ToolTipText = #f2f4f8 -PlaceholderText = #6ee8ecf2 -Accent = #4a82e6 - -[Palette.Disabled] -WindowText = #9d9d9d -Button = #18202e -Light = #3a4759 -Midlight = #333f50 -Dark = #141a23 -Mid = #252c38 -Text = #9d9d9d -BrightText = #8ec2ff -ButtonText = #787878 -Base = #18202e -Window = #18202e -Shadow = #0b0f15 -Highlight = #222d40 -HighlightedText = #9d9d9d -Link = #308cc6 -LinkVisited = #b450ff -AlternateBase = #1d2636 -ToolTipBase = #2b3546 -ToolTipText = #f2f4f8 -PlaceholderText = #6ee8ecf2 -Accent = #9d9d9d - -[Palette.Inactive] -WindowText = #e8ecf2 -Button = #2b374a -Light = #3a4759 -Midlight = #333f50 -Dark = #141a23 -Mid = #252c38 -Text = #e8ecf2 -BrightText = #8ec2ff -ButtonText = #f2f4f8 -Base = #222c3d -Window = #18202e -Shadow = #0b0f15 -Highlight = #222d40 -HighlightedText = #ffffff -Link = #7fb0f2 -LinkVisited = #a98ad9 -AlternateBase = #1d2636 -ToolTipBase = #2b3546 -ToolTipText = #f2f4f8 -PlaceholderText = #6ee8ecf2 -Accent = #4a82e6 - -[AppColors] -AccentStrong = #3a6fd6 -AccentSoft = #8fb8f2 - diff --git a/cockatrice/themes/Fabric/palette-default-light.toml b/cockatrice/themes/Fabric/palette-default-light.toml deleted file mode 100644 index f5151af3e..000000000 --- a/cockatrice/themes/Fabric/palette-default-light.toml +++ /dev/null @@ -1,74 +0,0 @@ -# Fabric — light identity palette (navy-cloth chrome) -[Palette] -WindowText = #1c2330 -Button = #c8d6ea -Light = #ffffff -Midlight = #b9c6dc -Dark = #8fa0bd -Mid = #a3b3cd -Text = #1c2330 -BrightText = #1d4fa0 -ButtonText = #141c29 -Base = #f6f8fc -Window = #dbe3ef -Shadow = #5a6a85 -Highlight = #2f6fd6 -HighlightedText = #ffffff -Link = #2555b0 -LinkVisited = #6a4bb8 -AlternateBase = #e9eef7 -ToolTipBase = #e9eff8 -ToolTipText = #141c29 -PlaceholderText = #7a1c2330 -Accent = #2f6fd6 - -[Palette.Disabled] -WindowText = #787878 -Button = #dbe3ef -Light = #ffffff -Midlight = #b9c6dc -Dark = #8fa0bd -Mid = #a3b3cd -Text = #787878 -BrightText = #1d4fa0 -ButtonText = #969696 -Base = #dbe3ef -Window = #dbe3ef -Shadow = #5a6a85 -Highlight = #cdd8e9 -HighlightedText = #787878 -Link = #0000ff -LinkVisited = #ff00ff -AlternateBase = #e9eef7 -ToolTipBase = #e9eff8 -ToolTipText = #141c29 -PlaceholderText = #7a1c2330 -Accent = #787878 - -[Palette.Inactive] -WindowText = #1c2330 -Button = #c8d6ea -Light = #ffffff -Midlight = #b9c6dc -Dark = #8fa0bd -Mid = #a3b3cd -Text = #1c2330 -BrightText = #1d4fa0 -ButtonText = #141c29 -Base = #f6f8fc -Window = #dbe3ef -Shadow = #5a6a85 -Highlight = #cdd8e9 -HighlightedText = #000000 -Link = #2555b0 -LinkVisited = #6a4bb8 -AlternateBase = #e9eef7 -ToolTipBase = #e9eff8 -ToolTipText = #141c29 -PlaceholderText = #7a1c2330 -Accent = #2f6fd6 - -[AppColors] -AccentStrong = #3a6fd6 -AccentSoft = #8fb8f2 - diff --git a/cockatrice/themes/Fabric/theme.cfg b/cockatrice/themes/Fabric/theme.cfg deleted file mode 100644 index 55b916e71..000000000 --- a/cockatrice/themes/Fabric/theme.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[Appearance] -ColorScheme = System - -[Style] -Name = Fusion \ No newline at end of file diff --git a/cockatrice/themes/Fabric/zones/handzone-light.png b/cockatrice/themes/Fabric/zones/handzone-light.png deleted file mode 100644 index a87f9e292..000000000 Binary files a/cockatrice/themes/Fabric/zones/handzone-light.png and /dev/null differ diff --git a/cockatrice/themes/Fabric/zones/handzone.png b/cockatrice/themes/Fabric/zones/handzone.png index a5b72286c..25d22b070 100644 Binary files a/cockatrice/themes/Fabric/zones/handzone.png and b/cockatrice/themes/Fabric/zones/handzone.png differ diff --git a/cockatrice/themes/Fabric/zones/playerzone-light.png b/cockatrice/themes/Fabric/zones/playerzone-light.png deleted file mode 100644 index 05da3ea9b..000000000 Binary files a/cockatrice/themes/Fabric/zones/playerzone-light.png and /dev/null differ diff --git a/cockatrice/themes/Fabric/zones/playerzone.png b/cockatrice/themes/Fabric/zones/playerzone.png index 7fa977249..728fccdfa 100644 Binary files a/cockatrice/themes/Fabric/zones/playerzone.png and b/cockatrice/themes/Fabric/zones/playerzone.png differ diff --git a/cockatrice/themes/Fabric/zones/stackzone-light.png b/cockatrice/themes/Fabric/zones/stackzone-light.png deleted file mode 100644 index dcd6dc045..000000000 Binary files a/cockatrice/themes/Fabric/zones/stackzone-light.png and /dev/null differ diff --git a/cockatrice/themes/Fabric/zones/stackzone.png b/cockatrice/themes/Fabric/zones/stackzone.png index 9046aa049..89e2080f8 100644 Binary files a/cockatrice/themes/Fabric/zones/stackzone.png and b/cockatrice/themes/Fabric/zones/stackzone.png differ diff --git a/cockatrice/themes/Fabric/zones/tablezone-light.png b/cockatrice/themes/Fabric/zones/tablezone-light.png deleted file mode 100644 index cb297e7e1..000000000 Binary files a/cockatrice/themes/Fabric/zones/tablezone-light.png and /dev/null differ diff --git a/cockatrice/themes/Fabric/zones/tablezone.png b/cockatrice/themes/Fabric/zones/tablezone.png index 8048a4cc1..6a2ce68e1 100644 Binary files a/cockatrice/themes/Fabric/zones/tablezone.png and b/cockatrice/themes/Fabric/zones/tablezone.png differ diff --git a/cockatrice/themes/Fusion/palette-default-dark.toml b/cockatrice/themes/Fusion/palette-default-dark.toml index b180e9b63..c1d83a4cd 100644 --- a/cockatrice/themes/Fusion/palette-default-dark.toml +++ b/cockatrice/themes/Fusion/palette-default-dark.toml @@ -11,15 +11,15 @@ ButtonText = #ffffffff Base = #ff2d2d2d Window = #ff1e1e1e Shadow = #ff000000 -Highlight = #ff139740 +Highlight = #ff148c3c HighlightedText = #ffffffff -Link = #ffc9fd62 -LinkVisited = #ff9ad43e +Link = #ff00f652 +LinkVisited = #ff00d346 AlternateBase = #ff353535 ToolTipBase = #ff3c3c3c ToolTipText = #ffd4d4d4 PlaceholderText = #80ffffff -Accent = #ffc9fd62 +Accent = #ff00d346 [Palette.Disabled] WindowText = #ff9d9d9d @@ -34,7 +34,7 @@ ButtonText = #ff9d9d9d Base = #ff1e1e1e Window = #ff1e1e1e Shadow = #ff000000 -Highlight = #ff139740 +Highlight = #ff148c3c HighlightedText = #ffffffff Link = #ff308cc6 LinkVisited = #ffff00ff @@ -59,15 +59,11 @@ Window = #ff1e1e1e Shadow = #ff000000 Highlight = #ff1e1e1e HighlightedText = #ffffffff -Link = #ffc9fd62 -LinkVisited = #ff9ad43e +Link = #ff00f652 +LinkVisited = #ff00d346 AlternateBase = #ff353535 ToolTipBase = #ff3c3c3c ToolTipText = #ffd4d4d4 PlaceholderText = #80ffffff Accent = #ff1e1e1e - -[AppColors] -AccentStrong = #ff139740 -AccentSoft = #ffc9fd62 diff --git a/cockatrice/themes/Fusion/palette-default-light.toml b/cockatrice/themes/Fusion/palette-default-light.toml index 5a1163a2b..86c41be78 100644 --- a/cockatrice/themes/Fusion/palette-default-light.toml +++ b/cockatrice/themes/Fusion/palette-default-light.toml @@ -11,15 +11,15 @@ ButtonText = #ff000000 Base = #ffffffff Window = #fff0f0f0 Shadow = #ff696969 -Highlight = #ff139740 +Highlight = #ff148c3c HighlightedText = #ffffffff -Link = #ff0e6b31 -LinkVisited = #ff0a521f +Link = #ff0d5f28 +LinkVisited = #ff08401b AlternateBase = #ffe9e7e3 ToolTipBase = #ffffffdc ToolTipText = #ff000000 PlaceholderText = #80000000 -Accent = #ff139740 +Accent = #ff107532 [Palette.Disabled] WindowText = #ff787878 @@ -34,7 +34,7 @@ ButtonText = #ff787878 Base = #fff0f0f0 Window = #fff0f0f0 Shadow = #ff000000 -Highlight = #ff139740 +Highlight = #ff148c3c HighlightedText = #ffffffff Link = #ff0000ff LinkVisited = #ffff00ff @@ -59,15 +59,11 @@ Window = #fff0f0f0 Shadow = #ff696969 Highlight = #fff0f0f0 HighlightedText = #ff000000 -Link = #ff0e6b31 -LinkVisited = #ff0a521f +Link = #ff0d5f28 +LinkVisited = #ff08401b AlternateBase = #ffe9e7e3 ToolTipBase = #ffffffdc ToolTipText = #ff000000 PlaceholderText = #80000000 Accent = #fff0f0f0 - -[AppColors] -AccentStrong = #ff139740 -AccentSoft = #ffc9fd62 diff --git a/cockatrice/themes/Leather/backgrounds/home-dark.png b/cockatrice/themes/Leather/backgrounds/home-dark.png deleted file mode 100644 index 20d16a27f..000000000 Binary files a/cockatrice/themes/Leather/backgrounds/home-dark.png and /dev/null differ diff --git a/cockatrice/themes/Leather/backgrounds/home-light.png b/cockatrice/themes/Leather/backgrounds/home-light.png deleted file mode 100644 index ee1da361d..000000000 Binary files a/cockatrice/themes/Leather/backgrounds/home-light.png and /dev/null differ diff --git a/cockatrice/themes/Leather/palette-default-dark.toml b/cockatrice/themes/Leather/palette-default-dark.toml deleted file mode 100644 index b0db0e90a..000000000 --- a/cockatrice/themes/Leather/palette-default-dark.toml +++ /dev/null @@ -1,74 +0,0 @@ -# Leather — dark identity palette (black-brown chrome with brass accents) -[Palette] -WindowText = #eee9e2 -Button = #332d26 -Light = #4a4239 -Midlight = #3d3730 -Dark = #131009 -Mid = #26221c -Text = #eee9e2 -BrightText = #e3c27e -ButtonText = #f5f1ea -Base = #26221d -Window = #1d1a16 -Shadow = #0d0b07 -Highlight = #4a5f8f -HighlightedText = #ffffff -Link = #cfa263 -LinkVisited = #9a7db8 -AlternateBase = #211d19 -ToolTipBase = #37302a -ToolTipText = #f5f1ea -PlaceholderText = #6eeee9e2 -Accent = #c9995a - -[Palette.Disabled] -WindowText = #9d9d9d -Button = #1d1a16 -Light = #4a4239 -Midlight = #3d3730 -Dark = #131009 -Mid = #26221c -Text = #9d9d9d -BrightText = #e3c27e -ButtonText = #787878 -Base = #1d1a16 -Window = #1d1a16 -Shadow = #0d0b07 -Highlight = #2d2822 -HighlightedText = #9d9d9d -Link = #308cc6 -LinkVisited = #b450ff -AlternateBase = #211d19 -ToolTipBase = #37302a -ToolTipText = #f5f1ea -PlaceholderText = #6eeee9e2 -Accent = #9d9d9d - -[Palette.Inactive] -WindowText = #eee9e2 -Button = #332d26 -Light = #4a4239 -Midlight = #3d3730 -Dark = #131009 -Mid = #26221c -Text = #eee9e2 -BrightText = #e3c27e -ButtonText = #f5f1ea -Base = #26221d -Window = #1d1a16 -Shadow = #0d0b07 -Highlight = #2d2822 -HighlightedText = #ffffff -Link = #cfa263 -LinkVisited = #9a7db8 -AlternateBase = #211d19 -ToolTipBase = #37302a -ToolTipText = #f5f1ea -PlaceholderText = #6eeee9e2 -Accent = #c9995a - -[AppColors] -AccentStrong = #b7823b -AccentSoft = #e4c58f - diff --git a/cockatrice/themes/Leather/palette-default-light.toml b/cockatrice/themes/Leather/palette-default-light.toml deleted file mode 100644 index ff122db76..000000000 --- a/cockatrice/themes/Leather/palette-default-light.toml +++ /dev/null @@ -1,74 +0,0 @@ -# Leather — light identity palette (black-brown chrome with brass accents) -[Palette] -WindowText = #2a2114 -Button = #dfcbb0 -Light = #ffffff -Midlight = #c6b08c -Dark = #a18a64 -Mid = #b29a74 -Text = #2a2114 -BrightText = #7a531c -ButtonText = #1f1710 -Base = #fdf8ee -Window = #f0e2cb -Shadow = #5d4d33 -Highlight = #34508c -HighlightedText = #ffffff -Link = #8a5e1e -LinkVisited = #6b5190 -AlternateBase = #f5ebd7 -ToolTipBase = #fff7e8 -ToolTipText = #1f1710 -PlaceholderText = #7a2a2114 -Accent = #a5712f - -[Palette.Disabled] -WindowText = #787878 -Button = #f0e2cb -Light = #ffffff -Midlight = #c6b08c -Dark = #a18a64 -Mid = #b29a74 -Text = #787878 -BrightText = #7a531c -ButtonText = #969696 -Base = #f0e2cb -Window = #f0e2cb -Shadow = #5d4d33 -Highlight = #ecd9bb -HighlightedText = #787878 -Link = #0000ff -LinkVisited = #ff00ff -AlternateBase = #f5ebd7 -ToolTipBase = #fff7e8 -ToolTipText = #1f1710 -PlaceholderText = #7a2a2114 -Accent = #787878 - -[Palette.Inactive] -WindowText = #2a2114 -Button = #dfcbb0 -Light = #ffffff -Midlight = #c6b08c -Dark = #a18a64 -Mid = #b29a74 -Text = #2a2114 -BrightText = #7a531c -ButtonText = #1f1710 -Base = #fdf8ee -Window = #f0e2cb -Shadow = #5d4d33 -Highlight = #ecd9bb -HighlightedText = #000000 -Link = #8a5e1e -LinkVisited = #6b5190 -AlternateBase = #f5ebd7 -ToolTipBase = #fff7e8 -ToolTipText = #1f1710 -PlaceholderText = #7a2a2114 -Accent = #a5712f - -[AppColors] -AccentStrong = #b7823b -AccentSoft = #e4c58f - diff --git a/cockatrice/themes/Leather/theme.cfg b/cockatrice/themes/Leather/theme.cfg deleted file mode 100644 index 55b916e71..000000000 --- a/cockatrice/themes/Leather/theme.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[Appearance] -ColorScheme = System - -[Style] -Name = Fusion \ No newline at end of file diff --git a/cockatrice/themes/Leather/zones/handzone-light.png b/cockatrice/themes/Leather/zones/handzone-light.png deleted file mode 100644 index b2c9bba34..000000000 Binary files a/cockatrice/themes/Leather/zones/handzone-light.png and /dev/null differ diff --git a/cockatrice/themes/Leather/zones/handzone.png b/cockatrice/themes/Leather/zones/handzone.png index 5f803df74..aba879683 100644 Binary files a/cockatrice/themes/Leather/zones/handzone.png and b/cockatrice/themes/Leather/zones/handzone.png differ diff --git a/cockatrice/themes/Leather/zones/playerzone-light.png b/cockatrice/themes/Leather/zones/playerzone-light.png deleted file mode 100644 index a26f96a5e..000000000 Binary files a/cockatrice/themes/Leather/zones/playerzone-light.png and /dev/null differ diff --git a/cockatrice/themes/Leather/zones/playerzone.png b/cockatrice/themes/Leather/zones/playerzone.png index cccbb3fd3..4f42ee41d 100644 Binary files a/cockatrice/themes/Leather/zones/playerzone.png and b/cockatrice/themes/Leather/zones/playerzone.png differ diff --git a/cockatrice/themes/Leather/zones/stackzone-light.png b/cockatrice/themes/Leather/zones/stackzone-light.png deleted file mode 100644 index 117c738f7..000000000 Binary files a/cockatrice/themes/Leather/zones/stackzone-light.png and /dev/null differ diff --git a/cockatrice/themes/Leather/zones/stackzone.png b/cockatrice/themes/Leather/zones/stackzone.png index d9cff892b..c02e2284a 100644 Binary files a/cockatrice/themes/Leather/zones/stackzone.png and b/cockatrice/themes/Leather/zones/stackzone.png differ diff --git a/cockatrice/themes/Leather/zones/tablezone-light.png b/cockatrice/themes/Leather/zones/tablezone-light.png deleted file mode 100644 index 912cbfb94..000000000 Binary files a/cockatrice/themes/Leather/zones/tablezone-light.png and /dev/null differ diff --git a/cockatrice/themes/Leather/zones/tablezone.png b/cockatrice/themes/Leather/zones/tablezone.png index 8af4c853d..152f4f7e9 100644 Binary files a/cockatrice/themes/Leather/zones/tablezone.png and b/cockatrice/themes/Leather/zones/tablezone.png differ diff --git a/cockatrice/themes/Plasma/backgrounds/home-dark.png b/cockatrice/themes/Plasma/backgrounds/home-dark.png deleted file mode 100644 index ce1469e2b..000000000 Binary files a/cockatrice/themes/Plasma/backgrounds/home-dark.png and /dev/null differ diff --git a/cockatrice/themes/Plasma/backgrounds/home-light.png b/cockatrice/themes/Plasma/backgrounds/home-light.png deleted file mode 100644 index 2e9000bd6..000000000 Binary files a/cockatrice/themes/Plasma/backgrounds/home-light.png and /dev/null differ diff --git a/cockatrice/themes/Plasma/palette-default-dark.toml b/cockatrice/themes/Plasma/palette-default-dark.toml deleted file mode 100644 index 222456f4f..000000000 --- a/cockatrice/themes/Plasma/palette-default-dark.toml +++ /dev/null @@ -1,74 +0,0 @@ -# Plasma — dark identity palette (electric violet chrome with cyan sparks) -[Palette] -WindowText = #eeebff -Button = #2a2440 -Light = #453d63 -Midlight = #383252 -Dark = #0e0c16 -Mid = #211e31 -Text = #eeebff -BrightText = #7fd7ff -ButtonText = #f6f4ff -Base = #1e1a2e -Window = #151220 -Shadow = #08070e -Highlight = #6a4df0 -HighlightedText = #ffffff -Link = #37c7e8 -LinkVisited = #9b7aff -AlternateBase = #1a1726 -ToolTipBase = #2c2745 -ToolTipText = #f6f4ff -PlaceholderText = #6eeeebff -Accent = #6a4df0 - -[Palette.Disabled] -WindowText = #9d9d9d -Button = #151220 -Light = #453d63 -Midlight = #383252 -Dark = #0e0c16 -Mid = #211e31 -Text = #9d9d9d -BrightText = #7fd7ff -ButtonText = #787878 -Base = #151220 -Window = #151220 -Shadow = #08070e -Highlight = #211c32 -HighlightedText = #9d9d9d -Link = #308cc6 -LinkVisited = #b450ff -AlternateBase = #1a1726 -ToolTipBase = #2c2745 -ToolTipText = #f6f4ff -PlaceholderText = #6eeeebff -Accent = #9d9d9d - -[Palette.Inactive] -WindowText = #eeebff -Button = #2a2440 -Light = #453d63 -Midlight = #383252 -Dark = #0e0c16 -Mid = #211e31 -Text = #eeebff -BrightText = #7fd7ff -ButtonText = #f6f4ff -Base = #1e1a2e -Window = #151220 -Shadow = #08070e -Highlight = #211c32 -HighlightedText = #ffffff -Link = #37c7e8 -LinkVisited = #9b7aff -AlternateBase = #1a1726 -ToolTipBase = #2c2745 -ToolTipText = #f6f4ff -PlaceholderText = #6eeeebff -Accent = #6a4df0 - -[AppColors] -AccentStrong = #5b3ee0 -AccentSoft = #a28bf7 - diff --git a/cockatrice/themes/Plasma/palette-default-light.toml b/cockatrice/themes/Plasma/palette-default-light.toml deleted file mode 100644 index d54db31d2..000000000 --- a/cockatrice/themes/Plasma/palette-default-light.toml +++ /dev/null @@ -1,74 +0,0 @@ -# Plasma — light identity palette (electric violet chrome with cyan sparks) -[Palette] -WindowText = #1d1830 -Button = #cfc6f0 -Light = #ffffff -Midlight = #b7adde -Dark = #9589c8 -Mid = #a49ad2 -Text = #1d1830 -BrightText = #10406e -ButtonText = #120d26 -Base = #f9f7ff -Window = #e3dcf7 -Shadow = #554c85 -Highlight = #5a3ee2 -HighlightedText = #ffffff -Link = #0f8fb5 -LinkVisited = #6a45d6 -AlternateBase = #ece8fa -ToolTipBase = #f5f3ff -ToolTipText = #120d26 -PlaceholderText = #7a1d1830 -Accent = #5a3ee2 - -[Palette.Disabled] -WindowText = #787878 -Button = #e3dcf7 -Light = #ffffff -Midlight = #b7adde -Dark = #9589c8 -Mid = #a49ad2 -Text = #787878 -BrightText = #10406e -ButtonText = #969696 -Base = #e3dcf7 -Window = #e3dcf7 -Shadow = #554c85 -Highlight = #d6ccf3 -HighlightedText = #787878 -Link = #0000ff -LinkVisited = #ff00ff -AlternateBase = #ece8fa -ToolTipBase = #f5f3ff -ToolTipText = #120d26 -PlaceholderText = #7a1d1830 -Accent = #787878 - -[Palette.Inactive] -WindowText = #1d1830 -Button = #cfc6f0 -Light = #ffffff -Midlight = #b7adde -Dark = #9589c8 -Mid = #a49ad2 -Text = #1d1830 -BrightText = #10406e -ButtonText = #120d26 -Base = #f9f7ff -Window = #e3dcf7 -Shadow = #554c85 -Highlight = #d6ccf3 -HighlightedText = #000000 -Link = #0f8fb5 -LinkVisited = #6a45d6 -AlternateBase = #ece8fa -ToolTipBase = #f5f3ff -ToolTipText = #120d26 -PlaceholderText = #7a1d1830 -Accent = #5a3ee2 - -[AppColors] -AccentStrong = #5b3ee0 -AccentSoft = #a28bf7 - diff --git a/cockatrice/themes/Plasma/theme.cfg b/cockatrice/themes/Plasma/theme.cfg deleted file mode 100644 index 55b916e71..000000000 --- a/cockatrice/themes/Plasma/theme.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[Appearance] -ColorScheme = System - -[Style] -Name = Fusion \ No newline at end of file diff --git a/cockatrice/themes/Plasma/zones/handzone-light.png b/cockatrice/themes/Plasma/zones/handzone-light.png deleted file mode 100644 index 89fa7e6d4..000000000 Binary files a/cockatrice/themes/Plasma/zones/handzone-light.png and /dev/null differ diff --git a/cockatrice/themes/Plasma/zones/handzone.png b/cockatrice/themes/Plasma/zones/handzone.png index 82debb405..543571d8a 100644 Binary files a/cockatrice/themes/Plasma/zones/handzone.png and b/cockatrice/themes/Plasma/zones/handzone.png differ diff --git a/cockatrice/themes/Plasma/zones/playerzone-light.png b/cockatrice/themes/Plasma/zones/playerzone-light.png deleted file mode 100644 index 9034818f2..000000000 Binary files a/cockatrice/themes/Plasma/zones/playerzone-light.png and /dev/null differ diff --git a/cockatrice/themes/Plasma/zones/playerzone.png b/cockatrice/themes/Plasma/zones/playerzone.png index 4623524f6..eebcd1f84 100644 Binary files a/cockatrice/themes/Plasma/zones/playerzone.png and b/cockatrice/themes/Plasma/zones/playerzone.png differ diff --git a/cockatrice/themes/Plasma/zones/stackzone-light.png b/cockatrice/themes/Plasma/zones/stackzone-light.png deleted file mode 100644 index 2607b3389..000000000 Binary files a/cockatrice/themes/Plasma/zones/stackzone-light.png and /dev/null differ diff --git a/cockatrice/themes/Plasma/zones/stackzone.png b/cockatrice/themes/Plasma/zones/stackzone.png index 144d991f3..1845c7091 100644 Binary files a/cockatrice/themes/Plasma/zones/stackzone.png and b/cockatrice/themes/Plasma/zones/stackzone.png differ diff --git a/cockatrice/themes/Plasma/zones/tablezone-light.png b/cockatrice/themes/Plasma/zones/tablezone-light.png deleted file mode 100644 index dcf2ef95e..000000000 Binary files a/cockatrice/themes/Plasma/zones/tablezone-light.png and /dev/null differ diff --git a/cockatrice/themes/Plasma/zones/tablezone.png b/cockatrice/themes/Plasma/zones/tablezone.png index 4dc36ec2d..8f998d4bb 100644 Binary files a/cockatrice/themes/Plasma/zones/tablezone.png and b/cockatrice/themes/Plasma/zones/tablezone.png differ diff --git a/cockatrice/themes/System/palette-default-light.toml b/cockatrice/themes/System/palette-default-light.toml deleted file mode 100644 index 14c215cdf..000000000 --- a/cockatrice/themes/System/palette-default-light.toml +++ /dev/null @@ -1,67 +0,0 @@ -[Palette] -WindowText = #ff000000 -Button = #fff0f0f0 -Light = #ffffffff -Midlight = #ffe3e3e3 -Dark = #ffa0a0a0 -Mid = #ffa0a0a0 -Text = #ff000000 -BrightText = #ffffffff -ButtonText = #ff000000 -Base = #ffffffff -Window = #fff0f0f0 -Shadow = #ff696969 -HighlightedText = #ffffffff -Link = #ff0d5f28 -LinkVisited = #ff08401b -AlternateBase = #ffe9e7e3 -ToolTipBase = #ffffffdc -ToolTipText = #ff000000 -PlaceholderText = #80000000 - -[Palette.Disabled] -WindowText = #ff787878 -Button = #fff0f0f0 -Light = #ffffffff -Midlight = #fff7f7f7 -Dark = #ffa0a0a0 -Mid = #ffa0a0a0 -Text = #ff787878 -BrightText = #ffffffff -ButtonText = #ff787878 -Base = #fff0f0f0 -Window = #fff0f0f0 -Shadow = #ff000000 -HighlightedText = #ffffffff -Link = #ff0000ff -LinkVisited = #ffff00ff -AlternateBase = #fff7f7f7 -ToolTipBase = #ffffffdc -ToolTipText = #ff000000 -PlaceholderText = #80000000 - -[Palette.Inactive] -WindowText = #ff000000 -Button = #fff0f0f0 -Light = #ffffffff -Midlight = #ffe3e3e3 -Dark = #ffa0a0a0 -Mid = #ffa0a0a0 -Text = #ff000000 -BrightText = #ffffffff -ButtonText = #ff000000 -Base = #ffffffff -Window = #fff0f0f0 -Shadow = #ff696969 -HighlightedText = #ff000000 -Link = #ff0d5f28 -LinkVisited = #ff08401b -AlternateBase = #ffe9e7e3 -ToolTipBase = #ffffffdc -ToolTipText = #ff000000 -PlaceholderText = #80000000 - - -[AppColors] -AccentStrong = #ff148c3c -AccentSoft = #ff78c850 \ No newline at end of file diff --git a/cockatrice/themes/VelvetMarble/backgrounds/home-dark.png b/cockatrice/themes/VelvetMarble/backgrounds/home-dark.png deleted file mode 100644 index 34738090f..000000000 Binary files a/cockatrice/themes/VelvetMarble/backgrounds/home-dark.png and /dev/null differ diff --git a/cockatrice/themes/VelvetMarble/backgrounds/home-light.png b/cockatrice/themes/VelvetMarble/backgrounds/home-light.png deleted file mode 100644 index de783e086..000000000 Binary files a/cockatrice/themes/VelvetMarble/backgrounds/home-light.png and /dev/null differ diff --git a/cockatrice/themes/VelvetMarble/palette-default-dark.toml b/cockatrice/themes/VelvetMarble/palette-default-dark.toml deleted file mode 100644 index 496943931..000000000 --- a/cockatrice/themes/VelvetMarble/palette-default-dark.toml +++ /dev/null @@ -1,74 +0,0 @@ -# VelvetMarble — dark identity palette (charcoal-velvet chrome with slate marble) -[Palette] -WindowText = #e8eaee -Button = #262a31 -Light = #3a3f47 -Midlight = #31363d -Dark = #0e1013 -Mid = #1f2329 -Text = #e8eaee -BrightText = #c3d8f2 -ButtonText = #f2f3f6 -Base = #1b1e23 -Window = #131519 -Shadow = #08090b -Highlight = #8fa6c0 -HighlightedText = #101318 -Link = #a9c6e8 -LinkVisited = #b9a8d9 -AlternateBase = #171a1f -ToolTipBase = #2d323a -ToolTipText = #f2f3f6 -PlaceholderText = #6ee8eaee -Accent = #8fa6c0 - -[Palette.Disabled] -WindowText = #9d9d9d -Button = #131519 -Light = #3a3f47 -Midlight = #31363d -Dark = #0e1013 -Mid = #1f2329 -Text = #9d9d9d -BrightText = #c3d8f2 -ButtonText = #787878 -Base = #131519 -Window = #131519 -Shadow = #08090b -Highlight = #1f2229 -HighlightedText = #9d9d9d -Link = #308cc6 -LinkVisited = #b450ff -AlternateBase = #171a1f -ToolTipBase = #2d323a -ToolTipText = #f2f3f6 -PlaceholderText = #6ee8eaee -Accent = #9d9d9d - -[Palette.Inactive] -WindowText = #e8eaee -Button = #262a31 -Light = #3a3f47 -Midlight = #31363d -Dark = #0e1013 -Mid = #1f2329 -Text = #e8eaee -BrightText = #c3d8f2 -ButtonText = #f2f3f6 -Base = #1b1e23 -Window = #131519 -Shadow = #08090b -Highlight = #1f2229 -HighlightedText = #ffffff -Link = #a9c6e8 -LinkVisited = #b9a8d9 -AlternateBase = #171a1f -ToolTipBase = #2d323a -ToolTipText = #f2f3f6 -PlaceholderText = #6ee8eaee -Accent = #8fa6c0 - -[AppColors] -AccentStrong = #54687e -AccentSoft = #9db0c4 - diff --git a/cockatrice/themes/VelvetMarble/palette-default-light.toml b/cockatrice/themes/VelvetMarble/palette-default-light.toml deleted file mode 100644 index 5323fd320..000000000 --- a/cockatrice/themes/VelvetMarble/palette-default-light.toml +++ /dev/null @@ -1,74 +0,0 @@ -# VelvetMarble — light identity palette (charcoal-velvet chrome with slate marble) -[Palette] -WindowText = #1b1d21 -Button = #c7d1dd -Light = #ffffff -Midlight = #b3becb -Dark = #93a0b1 -Mid = #a1adbc -Text = #1b1d21 -BrightText = #26465f -ButtonText = #121418 -Base = #f7f9fb -Window = #d9e0ea -Shadow = #57626f -Highlight = #54687e -HighlightedText = #ffffff -Link = #2e4d6b -LinkVisited = #5d4a78 -AlternateBase = #e6ebf2 -ToolTipBase = #f2f4f6 -ToolTipText = #121418 -PlaceholderText = #7a1b1d21 -Accent = #54687e - -[Palette.Disabled] -WindowText = #787878 -Button = #d9e0ea -Light = #ffffff -Midlight = #b3becb -Dark = #93a0b1 -Mid = #a1adbc -Text = #787878 -BrightText = #26465f -ButtonText = #969696 -Base = #d9e0ea -Window = #d9e0ea -Shadow = #57626f -Highlight = #ccd5e3 -HighlightedText = #787878 -Link = #0000ff -LinkVisited = #ff00ff -AlternateBase = #e6ebf2 -ToolTipBase = #f2f4f6 -ToolTipText = #121418 -PlaceholderText = #7a1b1d21 -Accent = #787878 - -[Palette.Inactive] -WindowText = #1b1d21 -Button = #c7d1dd -Light = #ffffff -Midlight = #b3becb -Dark = #93a0b1 -Mid = #a1adbc -Text = #1b1d21 -BrightText = #26465f -ButtonText = #121418 -Base = #f7f9fb -Window = #d9e0ea -Shadow = #57626f -Highlight = #ccd5e3 -HighlightedText = #000000 -Link = #2e4d6b -LinkVisited = #5d4a78 -AlternateBase = #e6ebf2 -ToolTipBase = #f2f4f6 -ToolTipText = #121418 -PlaceholderText = #7a1b1d21 -Accent = #54687e - -[AppColors] -AccentStrong = #54687e -AccentSoft = #9db0c4 - diff --git a/cockatrice/themes/VelvetMarble/theme.cfg b/cockatrice/themes/VelvetMarble/theme.cfg deleted file mode 100644 index 55b916e71..000000000 --- a/cockatrice/themes/VelvetMarble/theme.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[Appearance] -ColorScheme = System - -[Style] -Name = Fusion \ No newline at end of file diff --git a/cockatrice/themes/VelvetMarble/zones/handzone-light.png b/cockatrice/themes/VelvetMarble/zones/handzone-light.png deleted file mode 100644 index b21e78e61..000000000 Binary files a/cockatrice/themes/VelvetMarble/zones/handzone-light.png and /dev/null differ diff --git a/cockatrice/themes/VelvetMarble/zones/handzone.jpg b/cockatrice/themes/VelvetMarble/zones/handzone.jpg new file mode 100644 index 000000000..2ec9b37fe Binary files /dev/null and b/cockatrice/themes/VelvetMarble/zones/handzone.jpg differ diff --git a/cockatrice/themes/VelvetMarble/zones/handzone.png b/cockatrice/themes/VelvetMarble/zones/handzone.png deleted file mode 100644 index 3e2b7ebea..000000000 Binary files a/cockatrice/themes/VelvetMarble/zones/handzone.png and /dev/null differ diff --git a/cockatrice/themes/VelvetMarble/zones/playerzone-light.png b/cockatrice/themes/VelvetMarble/zones/playerzone-light.png deleted file mode 100644 index 630aac6eb..000000000 Binary files a/cockatrice/themes/VelvetMarble/zones/playerzone-light.png and /dev/null differ diff --git a/cockatrice/themes/VelvetMarble/zones/playerzone.jpg b/cockatrice/themes/VelvetMarble/zones/playerzone.jpg new file mode 100644 index 000000000..dd13cab78 Binary files /dev/null and b/cockatrice/themes/VelvetMarble/zones/playerzone.jpg differ diff --git a/cockatrice/themes/VelvetMarble/zones/playerzone.png b/cockatrice/themes/VelvetMarble/zones/playerzone.png deleted file mode 100644 index dcf21ba38..000000000 Binary files a/cockatrice/themes/VelvetMarble/zones/playerzone.png and /dev/null differ diff --git a/cockatrice/themes/VelvetMarble/zones/stackzone-light.png b/cockatrice/themes/VelvetMarble/zones/stackzone-light.png deleted file mode 100644 index 62027827c..000000000 Binary files a/cockatrice/themes/VelvetMarble/zones/stackzone-light.png and /dev/null differ diff --git a/cockatrice/themes/VelvetMarble/zones/stackzone.jpg b/cockatrice/themes/VelvetMarble/zones/stackzone.jpg new file mode 100644 index 000000000..b63aa0902 Binary files /dev/null and b/cockatrice/themes/VelvetMarble/zones/stackzone.jpg differ diff --git a/cockatrice/themes/VelvetMarble/zones/stackzone.png b/cockatrice/themes/VelvetMarble/zones/stackzone.png deleted file mode 100644 index a47f7bf9d..000000000 Binary files a/cockatrice/themes/VelvetMarble/zones/stackzone.png and /dev/null differ diff --git a/cockatrice/themes/VelvetMarble/zones/tablezone-light.png b/cockatrice/themes/VelvetMarble/zones/tablezone-light.png deleted file mode 100644 index 5a9014f9d..000000000 Binary files a/cockatrice/themes/VelvetMarble/zones/tablezone-light.png and /dev/null differ diff --git a/cockatrice/themes/VelvetMarble/zones/tablezone.jpg b/cockatrice/themes/VelvetMarble/zones/tablezone.jpg new file mode 100644 index 000000000..9f511491a Binary files /dev/null and b/cockatrice/themes/VelvetMarble/zones/tablezone.jpg differ diff --git a/cockatrice/themes/VelvetMarble/zones/tablezone.png b/cockatrice/themes/VelvetMarble/zones/tablezone.png deleted file mode 100644 index 1523d0821..000000000 Binary files a/cockatrice/themes/VelvetMarble/zones/tablezone.png and /dev/null differ diff --git a/doc/carddatabase_v4/cards.xsd b/doc/carddatabase_v4/cards.xsd index 82aed32ab..59ca3e560 100644 --- a/doc/carddatabase_v4/cards.xsd +++ b/doc/carddatabase_v4/cards.xsd @@ -35,13 +35,6 @@ - - - - - - - @@ -56,13 +49,6 @@ - - - - - - - diff --git a/libcockatrice_card/CMakeLists.txt b/libcockatrice_card/CMakeLists.txt index 15388ecfc..081e6fd05 100644 --- a/libcockatrice_card/CMakeLists.txt +++ b/libcockatrice_card/CMakeLists.txt @@ -5,7 +5,6 @@ set(CMAKE_AUTORCC ON) set(HEADERS libcockatrice/card/card_info.h libcockatrice/card/card_info_comparator.h - libcockatrice/card/card_localization.h libcockatrice/card/lazy_properties_hash.h libcockatrice/card/database/card_database.h libcockatrice/card/database/card_database_loader.h @@ -28,7 +27,6 @@ add_library( ${MOC_SOURCES} libcockatrice/card/card_info.cpp libcockatrice/card/card_info_comparator.cpp - libcockatrice/card/card_localization.cpp libcockatrice/card/lazy_properties_hash.cpp libcockatrice/card/database/card_database.cpp libcockatrice/card/database/card_database_cache.cpp diff --git a/libcockatrice_card/libcockatrice/card/card_info.cpp b/libcockatrice_card/libcockatrice/card/card_info.cpp index 56c737793..786e17950 100644 --- a/libcockatrice_card/libcockatrice/card/card_info.cpp +++ b/libcockatrice_card/libcockatrice/card/card_info.cpp @@ -40,11 +40,8 @@ CardInfo::CardInfo(const QString &_name, const QList &_relatedCards, const QList &_reverseRelatedCards, SetToPrintingsMap _sets, - const UiAttributes _uiAttributes, - QMap _localizedNames, - QMap _localizedTexts) - : name(_name), text(_text), isToken(_isToken), localizedNames(std::move(_localizedNames)), - localizedTexts(std::move(_localizedTexts)), properties(LazyPropertiesHash(_properties)), + const UiAttributes _uiAttributes) + : name(_name), text(_text), isToken(_isToken), properties(LazyPropertiesHash(_properties)), relatedCards(_relatedCards), reverseRelatedCards(_reverseRelatedCards), setsToPrintings(std::move(_sets)), uiAttributes(_uiAttributes) { @@ -62,11 +59,8 @@ CardInfo::CardInfo(const QString &_name, SetToPrintingsMap _sets, const UiAttributes _uiAttributes, QString _simpleName, - QSet _altNames, - QMap _localizedNames, - QMap _localizedTexts) + QSet _altNames) : name(_name), simpleName(std::move(_simpleName)), text(_text), isToken(_isToken), - localizedNames(std::move(_localizedNames)), localizedTexts(std::move(_localizedTexts)), properties(LazyPropertiesHash(_propertiesBlob)), relatedCards(_relatedCards), reverseRelatedCards(_reverseRelatedCards), setsToPrintings(std::move(_sets)), uiAttributes(_uiAttributes), altNames(std::move(_altNames)) @@ -89,12 +83,10 @@ CardInfoPtr CardInfo::newInstance(const QString &_name, const QList &_relatedCards, const QList &_reverseRelatedCards, SetToPrintingsMap _sets, - const UiAttributes _uiAttributes, - QMap _localizedNames, - QMap _localizedTexts) + const UiAttributes _uiAttributes) { - CardInfoPtr ptr(new CardInfo(_name, _text, _isToken, _properties, _relatedCards, _reverseRelatedCards, _sets, - _uiAttributes, std::move(_localizedNames), std::move(_localizedTexts))); + CardInfoPtr ptr( + new CardInfo(_name, _text, _isToken, _properties, _relatedCards, _reverseRelatedCards, _sets, _uiAttributes)); ptr->setSmartPointer(ptr); for (const auto &printings : _sets) { @@ -117,13 +109,11 @@ CardInfoPtr CardInfo::newInstance(const QString &_name, const UiAttributes _uiAttributes, QString _simpleName, QSet _altNames, - bool _appendToSets, - QMap _localizedNames, - QMap _localizedTexts) + bool _appendToSets) { CardInfoPtr ptr(new CardInfo(_name, _text, _isToken, std::move(_propertiesBlob), _relatedCards, _reverseRelatedCards, _sets, _uiAttributes, std::move(_simpleName), - std::move(_altNames), std::move(_localizedNames), std::move(_localizedTexts))); + std::move(_altNames))); ptr->setSmartPointer(ptr); if (_appendToSets) { diff --git a/libcockatrice_card/libcockatrice/card/card_info.h b/libcockatrice_card/libcockatrice/card/card_info.h index 17894cec5..392dc3849 100644 --- a/libcockatrice_card/libcockatrice/card/card_info.h +++ b/libcockatrice_card/libcockatrice/card/card_info.h @@ -77,9 +77,6 @@ private: QString text; ///< Text description or rules text of the card. bool isToken; ///< Whether this card is a token or not. - QMap localizedNames; ///< Localized card names, keyed by language code. - QMap localizedTexts; ///< Localized rules text, keyed by language code. - LazyPropertiesHash properties; ///< Key-value store of dynamic card properties. QList relatedCards; ///< Forward references to related cards. @@ -103,8 +100,6 @@ public: * @param _reverseRelatedCards Backward references to related cards. * @param _sets Map of set names to printing information. * @param _uiAttributes Attributes that affect display and game logic - * @param _localizedNames Localized card names, keyed by language code. - * @param _localizedTexts Localized rules text, keyed by language code. */ explicit CardInfo(const QString &_name, const QString &_text, @@ -113,9 +108,7 @@ public: const QList &_relatedCards, const QList &_reverseRelatedCards, SetToPrintingsMap _sets, - UiAttributes _uiAttributes, - QMap _localizedNames = {}, - QMap _localizedTexts = {}); + UiAttributes _uiAttributes); /** * @brief Constructs a CardInfo from a cache snapshot with precomputed derived @@ -137,8 +130,6 @@ public: * @param _uiAttributes Attributes that affect display and game logic. * @param _simpleName Precomputed simplified name. * @param _altNames Precomputed alternate names. - * @param _localizedNames Localized card names, keyed by language code. - * @param _localizedTexts Localized rules text, keyed by language code. */ explicit CardInfo(const QString &_name, const QString &_text, @@ -149,9 +140,7 @@ public: SetToPrintingsMap _sets, UiAttributes _uiAttributes, QString _simpleName, - QSet _altNames, - QMap _localizedNames = {}, - QMap _localizedTexts = {}); + QSet _altNames); /** * @brief Copy constructor for CardInfo. @@ -162,8 +151,7 @@ public: */ CardInfo(const CardInfo &other) : QObject(other.parent()), name(other.name), simpleName(other.simpleName), text(other.text), - isToken(other.isToken), localizedNames(other.localizedNames), localizedTexts(other.localizedTexts), - properties(other.properties), relatedCards(other.relatedCards), + isToken(other.isToken), properties(other.properties), relatedCards(other.relatedCards), reverseRelatedCards(other.reverseRelatedCards), reverseRelatedCardsToMe(other.reverseRelatedCardsToMe), setsToPrintings(other.setsToPrintings), uiAttributes(other.uiAttributes), setsNames(other.setsNames), altNames(other.altNames) @@ -191,8 +179,6 @@ public: * @param _reverseRelatedCards Reverse relationships. * @param _sets Printing information per set. * @param _uiAttributes Attributes that affect display and game logic - * @param _localizedNames Localized card names, keyed by language code. - * @param _localizedTexts Localized rules text, keyed by language code. * @return Shared pointer to the new CardInfo instance. */ static CardInfoPtr newInstance(const QString &_name, @@ -202,9 +188,7 @@ public: const QList &_relatedCards, const QList &_reverseRelatedCards, SetToPrintingsMap _sets, - UiAttributes _uiAttributes, - QMap _localizedNames = {}, - QMap _localizedTexts = {}); + UiAttributes _uiAttributes); /** * @brief Creates a new instance from a cache snapshot with precomputed @@ -224,8 +208,6 @@ public: * its CardSets. Pass false when building cards in parallel so the * (non-thread-safe) set membership is populated in a later * single-threaded pass. - * @param _localizedNames Localized card names, keyed by language code. - * @param _localizedTexts Localized rules text, keyed by language code. * @return Shared pointer to the new CardInfo instance. */ static CardInfoPtr newInstance(const QString &_name, @@ -238,9 +220,7 @@ public: UiAttributes _uiAttributes, QString _simpleName, QSet _altNames, - bool _appendToSets = true, - QMap _localizedNames = {}, - QMap _localizedTexts = {}); + bool _appendToSets = true); /** * @brief Clones the current CardInfo instance. @@ -290,96 +270,6 @@ public: text = _text; emit cardInfoChanged(smartThis); } - - /** - * @brief Returns the card name in the given language, falling back to the - * English name when no localization is available. - * - * @param lang Language code (e.g. "de", "ja", "zhs"). - * @return The localized name, or the English name as fallback. - */ - [[nodiscard]] const QString &getLocalizedName(const QString &lang) const - { - const auto it = localizedNames.constFind(lang); - return it != localizedNames.constEnd() ? it.value() : name; - } - - /** - * @brief Returns the rules text in the given language, falling back to the - * English text when no localization is available. - * - * @param lang Language code (e.g. "de", "ja", "zhs"). - * @return The localized text, or the English text as fallback. - */ - [[nodiscard]] const QString &getLocalizedText(const QString &lang) const - { - const auto it = localizedTexts.constFind(lang); - return it != localizedTexts.constEnd() ? it.value() : text; - } - - /** - * @brief Returns the localized card names keyed by language code. - * - * Only languages that have an entry are present; there is no English - * fallback in this map. - */ - [[nodiscard]] const QMap &getLocalizedNames() const - { - return localizedNames; - } - - /** - * @brief Returns the localized rules text keyed by language code. - * - * Only languages that have an entry are present; there is no English - * fallback in this map. - */ - [[nodiscard]] const QMap &getLocalizedTexts() const - { - return localizedTexts; - } - - /** - * @brief Sets the card name for the given language. - * - * @param lang Language code. - * @param _localizedName The localized card name. - */ - void setLocalizedName(const QString &lang, const QString &_localizedName) - { - if (localizedNames.value(lang) == _localizedName) { - return; - } - localizedNames.insert(lang, _localizedName); - emit cardInfoChanged(smartThis); - } - - /** - * @brief Sets the rules text for the given language. - * - * @param lang Language code. - * @param _localizedText The localized rules text. - */ - void setLocalizedText(const QString &lang, const QString &_localizedText) - { - if (localizedTexts.value(lang) == _localizedText) { - return; - } - localizedTexts.insert(lang, _localizedText); - emit cardInfoChanged(smartThis); - } - - /** - * @brief Returns the language codes for which this card has a localized - * name or rules text. - */ - [[nodiscard]] QStringList localizationLanguages() const - { - QStringList languages = localizedNames.keys(); - languages.append(localizedTexts.keys()); - languages.removeDuplicates(); - return languages; - } [[nodiscard]] bool getIsToken() const { return isToken; diff --git a/libcockatrice_card/libcockatrice/card/card_localization.cpp b/libcockatrice_card/libcockatrice/card/card_localization.cpp deleted file mode 100644 index 03c6c659b..000000000 --- a/libcockatrice_card/libcockatrice/card/card_localization.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "card_localization.h" - -#include -#include -#include - -namespace CardLocalization -{ -const QStringList &supportedLanguages() -{ - static const QStringList languages = {"cs", "de", "es", "fr", "it", "ja", "ko", "pt", "ru", "zhs", "zht", "he"}; - return languages; -} - -QString languageDisplayName(const QString &lang) -{ - static const QHash displayNames = { - {"cs", "Česky (Czech)"}, - {"de", "Deutsch (German)"}, - {"es", "Español (Spanish)"}, - {"fr", "Français (French)"}, - {"it", "Italiano (Italian)"}, - {"ja", "日本語 (Japanese)"}, - {"ko", "한국어 (Korean)"}, - {"pt", "Português (Portuguese)"}, - {"ru", "Русский (Russian)"}, - {"he", "עברית (Hebrew)"}, - {"zhs", "简体中文 (Chinese Simplified)"}, - {"zht", "繁體中文 (Chinese Traditional)"}, - }; - const QString displayName = displayNames.value(lang); - if (!displayName.isEmpty()) { - return displayName; - } - const QString nativeName = QLocale(lang).nativeLanguageName(); - return nativeName.isEmpty() ? lang : nativeName; -} -} // namespace CardLocalization \ No newline at end of file diff --git a/libcockatrice_card/libcockatrice/card/card_localization.h b/libcockatrice_card/libcockatrice/card/card_localization.h deleted file mode 100644 index a9c8d28ea..000000000 --- a/libcockatrice_card/libcockatrice/card/card_localization.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef CARD_LOCALIZATION_H -#define CARD_LOCALIZATION_H - -#include -#include - -/** - * @namespace CardLocalization - * @ingroup Cards - * - * @brief Shared language metadata for localized card text and images. - * - * Lists the language codes Cockatrice can display localized card data for and - * provides human-readable names. The list is shared between Oracle (which - * imports the selected language's card data) and the client settings UI (which - * offers the language choice). - */ -namespace CardLocalization -{ -/** - * @brief Language codes for which localized card data can be imported/displayed. - * - * Matches the languages Scryfall can serve localized card images for. "en" is - * always available as the default/fallback and is not listed here. - * - * @return The list of supported language codes. - */ -[[nodiscard]] const QStringList &supportedLanguages(); - -/** - * @brief Human-readable name for a language code. - * - * Follows the same "native name (English name)" format the UI language list - * uses (e.g. "日本語 (Japanese)"), so the English fallback is always visible. - * - * @param lang Language code (e.g. "de", "ja", "zhs"). - * @return The language's native name with its English name in parentheses, or - * the code itself if it cannot be resolved. - */ -[[nodiscard]] QString languageDisplayName(const QString &lang); -} // namespace CardLocalization - -#endif // CARD_LOCALIZATION_H \ No newline at end of file diff --git a/libcockatrice_card/libcockatrice/card/database/card_database_cache.cpp b/libcockatrice_card/libcockatrice/card/database/card_database_cache.cpp index 3165ff871..2b27f50f8 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database_cache.cpp +++ b/libcockatrice_card/libcockatrice/card/database/card_database_cache.cpp @@ -17,7 +17,7 @@ namespace { constexpr quint32 CACHE_MAGIC = 0x43445243; // "CDRC" -constexpr quint32 CACHE_VERSION = 3; +constexpr quint32 CACHE_VERSION = 2; // ---- Primitives ----------------------------------------------------------- @@ -71,35 +71,6 @@ QDate readDate(QDataStream &in) return d; } -void writeStringMap(QDataStream &out, const QMap &map) -{ - out << static_cast(map.size()); - for (auto it = map.constBegin(); it != map.constEnd(); ++it) { - writeString(out, it.key()); - writeString(out, it.value()); - } -} - -QMap readStringMap(QDataStream &in) -{ - QMap map; - quint32 count = 0; - in >> count; - if (in.status() != QDataStream::Ok) { - return map; - } - for (quint32 i = 0; i < count; ++i) { - QString key = readString(in); - QString value = readString(in); - if (in.status() != QDataStream::Ok) { - map.clear(); - return map; - } - map.insert(key, value); - } - return map; -} - // ---- CardRelation ---------------------------------------------------------- void writeRelation(QDataStream &out, const CardRelation *rel) @@ -224,10 +195,6 @@ void writeCard(QDataStream &out, const CardInfoPtr &card) for (const CardRelation *rel : reverse) { writeRelation(out, rel); } - - // localized card data - writeStringMap(out, card->getLocalizedNames()); - writeStringMap(out, card->getLocalizedTexts()); } CardInfoPtr readCard(QDataStream &in, const SetNameMap &sets) @@ -301,19 +268,8 @@ CardInfoPtr readCard(QDataStream &in, const SetNameMap &sets) reverse.append(readRelation(in)); } - const QMap localizedNames = readStringMap(in); - if (in.status() != QDataStream::Ok) { - return nullptr; - } - const QMap localizedTexts = readStringMap(in); - if (in.status() != QDataStream::Ok) { - return nullptr; - } - - CardInfoPtr card = CardInfo::newInstance(name, text, isToken, propertiesBlob, related, reverse, cardSets, ui, - simpleName, altNames, false, localizedNames, localizedTexts); - - return card; + return CardInfo::newInstance(name, text, isToken, propertiesBlob, related, reverse, cardSets, ui, simpleName, + altNames, false); } // ---- FormatRules ----------------------------------------------------------- diff --git a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp index 19e972a7a..ec460d685 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp +++ b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp @@ -273,8 +273,6 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml) QString name = QString(""); QString text = QString(""); QHash properties; - QMap localizedNames; - QMap localizedTexts; QList relatedCards, reverseRelatedCards; auto _sets = SetToPrintingsMap(); int tableRow = 0; @@ -300,44 +298,6 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml) // generic properties } else if (xmlName == "prop") { properties = loadCardPropertiesFromXml(xml); - // localized card data - } else if (xmlName == "localizations") { - while (!xml.atEnd()) { - if (xml.readNextStartElement()) { - const QString elementName = xml.name().toString(); - if (elementName == "localization") { - const QString lang = xml.attributes().value("lang").toString(); - QString localizedName; - QString localizedText; - while (!xml.atEnd()) { - if (xml.readNext() == QXmlStreamReader::EndElement) { - break; - } - if (xml.isStartElement()) { - const QString childName = xml.name().toString(); - QString value = xml.readElementText(QXmlStreamReader::IncludeChildElements); - if (childName == "name") { - localizedName = value; - } else if (childName == "text") { - localizedText = value; - } - } - } - if (!lang.isEmpty()) { - if (!localizedName.isEmpty()) { - localizedNames.insert(lang, localizedName); - } - if (!localizedText.isEmpty()) { - localizedTexts.insert(lang, localizedText); - } - } - } else { - xml.skipCurrentElement(); - } - } else { - break; - } - } // positioning info } else if (xmlName == "tablerow") { tableRow = xml.readElementText(QXmlStreamReader::IncludeChildElements).toInt(); @@ -439,9 +399,8 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml) .landscapeOrientation = landscapeOrientation, .tableRow = tableRow, .upsideDownArt = upsideDown}; - CardInfoPtr newCard = - CardInfo::newInstance(name, text, isToken, properties, relatedCards, reverseRelatedCards, _sets, - attributes, std::move(localizedNames), std::move(localizedTexts)); + CardInfoPtr newCard = CardInfo::newInstance(name, text, isToken, properties, relatedCards, + reverseRelatedCards, _sets, attributes); if (targetData) { // Mirror CardDatabase::addCard: if a card with this name already // exists, merge the new printings into it instead of replacing. @@ -558,26 +517,6 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in } xml.writeEndElement(); - // localized card data - const QStringList localizedLanguages = info->localizationLanguages(); - if (!localizedLanguages.isEmpty()) { - xml.writeStartElement("localizations"); - const QMap &localizedNames = info->getLocalizedNames(); - const QMap &localizedTexts = info->getLocalizedTexts(); - for (const QString &lang : localizedLanguages) { - xml.writeStartElement("localization"); - xml.writeAttribute("lang", lang); - if (localizedNames.contains(lang)) { - xml.writeTextElement("name", localizedNames.value(lang)); - } - if (localizedTexts.contains(lang)) { - xml.writeTextElement("text", localizedTexts.value(lang)); - } - xml.writeEndElement(); - } - xml.writeEndElement(); - } - // sets for (const auto &printings : info->getSets()) { for (const PrintingInfo &set : printings) { diff --git a/libcockatrice_deck_list/CMakeLists.txt b/libcockatrice_deck_list/CMakeLists.txt index 0c487466c..c7a54a390 100644 --- a/libcockatrice_deck_list/CMakeLists.txt +++ b/libcockatrice_deck_list/CMakeLists.txt @@ -11,7 +11,6 @@ set(HEADERS libcockatrice/deck_list/deck_list_history_manager.h libcockatrice/deck_list/deck_list_node_tree.h libcockatrice/deck_list/deck_list_memento.h - libcockatrice/deck_list/deck_list_plain_text_parser.h libcockatrice/deck_list/playmat_resolver.h libcockatrice/deck_list/sideboard_plan.h ) @@ -28,7 +27,6 @@ add_library( libcockatrice/deck_list/deck_list.cpp libcockatrice/deck_list/deck_list_history_manager.cpp libcockatrice/deck_list/deck_list_node_tree.cpp - libcockatrice/deck_list/deck_list_plain_text_parser.cpp libcockatrice/deck_list/playmat_resolver.cpp libcockatrice/deck_list/sideboard_plan.cpp ) diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp index 90f63c09d..1a3876cd3 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp @@ -1,7 +1,6 @@ #include "deck_list.h" #include "deck_list_memento.h" -#include "deck_list_plain_text_parser.h" #include "tree/abstract_deck_list_node.h" #include "tree/deck_list_card_node.h" #include "tree/inner_deck_list_node.h" @@ -9,138 +8,26 @@ #include #include #include +#include #include #include #include +#if QT_VERSION < 0x050600 +// qHash on QRegularExpression was added in 5.6, FIX IT +uint qHash(const QRegularExpression &key, uint seed) noexcept +{ + return qHash(key.pattern(), seed); // call qHash on pattern QString instead +} +#endif + static const QString CURRENT_SIDEBOARD_PLAN_KEY = ""; -/** - * @brief Parses a floating point XML attribute into a clamped playmat parameter. - * - * Falls back to @p fallback when the attribute is missing or malformed, so - * malformed deck files cannot produce degenerate art rectangles (e.g. a zoom - * of 0 dividing by zero). - * - * @param valueString Raw attribute text. - * @param fallback Value used when the text cannot be parsed. - * @param min Lower clamp bound. - * @param max Upper clamp bound. - * @return The parsed value clamped to [min, max], or @p fallback. - */ -static double parseClampedParam(const QString &valueString, double fallback, double min, double max) -{ - bool ok = false; - const double value = valueString.toDouble(&ok); - if (!ok) { - return fallback; - } - return qBound(min, value, max); -} - -/** - * @brief Reads a `bannerCard` element from the XML stream. - * - * @param xml Reader positioned at the element. - * @return The referenced card. - */ -static CardRef readBannerCard(QXmlStreamReader *xml) -{ - QString providerId = xml->attributes().value("providerId").toString(); - QString cardName = xml->readElementText(); - return {cardName, providerId}; -} - -/** - * @brief Reads a `playmatCard` element from the XML stream. - * - * Attribute values are read before readElementText consumes the element, and - * the params are clamped to the same ranges as the settings dialog and the - * remote player-properties path so malformed deck files cannot produce - * degenerate art rectangles (e.g. a zoom of 0 dividing by zero). - * - * @param xml Reader positioned at the element. - * @return The referenced card plus its clamped positioning parameters. - */ -static PlaymatInfo readPlaymatCard(QXmlStreamReader *xml) -{ - QString providerId = xml->attributes().value("providerId").toString(); - QString marginLStr = xml->attributes().value("marginPctL").toString(); - QString marginRStr = xml->attributes().value("marginPctR").toString(); - QString vOffStr = xml->attributes().value("verticalOffset").toString(); - QString zoomStr = xml->attributes().value("zoom").toString(); - QString cardName = xml->readElementText(); - - return { - .card = {cardName, providerId}, - .params = {.marginPctL = parseClampedParam(marginLStr, 0.07, 0.0, 0.95), - .marginPctR = parseClampedParam(marginRStr, 0.07, 0.0, 0.95), - .verticalOffset = parseClampedParam(vOffStr, 0.33, 0.0, 1.0), - .zoom = parseClampedParam(zoomStr, 1.0, 0.1, 4.0)}, - }; -} - bool DeckList::Metadata::isEmpty() const { return name.isEmpty() && comments.isEmpty() && bannerCard.isEmpty() && tags.isEmpty() && playmat.card.isEmpty(); } -bool DeckList::Metadata::readElement(QXmlStreamReader *xml, const QString &childName) -{ - if (childName == "lastLoadedTimestamp") { - lastLoadedTimestamp = xml->readElementText(); - } else if (childName == "deckname") { - name = xml->readElementText(); - } else if (childName == "format") { - gameFormat = xml->readElementText(); - } else if (childName == "comments") { - comments = xml->readElementText(); - } else if (childName == "bannerCard") { - bannerCard = readBannerCard(xml); - } else if (childName == "playmatCard") { - playmat = readPlaymatCard(xml); - } else if (childName == "tags") { - tags.clear(); // Clear existing tags - while (xml->readNextStartElement()) { - if (xml->name().toString() == "tag") { - tags.append(xml->readElementText()); - } - } - } else { - return false; - } - return true; -} - -void DeckList::Metadata::write(QXmlStreamWriter *xml) const -{ - xml->writeTextElement("lastLoadedTimestamp", lastLoadedTimestamp); - xml->writeTextElement("deckname", name); - xml->writeTextElement("format", gameFormat); - xml->writeStartElement("bannerCard"); - xml->writeAttribute("providerId", bannerCard.providerId); - xml->writeCharacters(bannerCard.name); - xml->writeEndElement(); - if (!playmat.card.isEmpty()) { - xml->writeStartElement("playmatCard"); - xml->writeAttribute("providerId", playmat.card.providerId); - xml->writeAttribute("marginPctL", QString::number(playmat.params.marginPctL, 'f', 4)); - xml->writeAttribute("marginPctR", QString::number(playmat.params.marginPctR, 'f', 4)); - xml->writeAttribute("verticalOffset", QString::number(playmat.params.verticalOffset, 'f', 4)); - xml->writeAttribute("zoom", QString::number(playmat.params.zoom, 'f', 4)); - xml->writeCharacters(playmat.card.name); - xml->writeEndElement(); - } - xml->writeTextElement("comments", comments); - - // Write tags - xml->writeStartElement("tags"); - for (const QString &tag : tags) { - xml->writeTextElement("tag", tag); - } - xml->writeEndElement(); -} - DeckList::DeckList() { } @@ -175,10 +62,56 @@ bool DeckList::readElement(QXmlStreamReader *xml) { const QString childName = xml->name().toString(); if (xml->isStartElement()) { - if (metadata.readElement(xml, childName)) { - return true; - } - if (childName == "zone") { + if (childName == "lastLoadedTimestamp") { + metadata.lastLoadedTimestamp = xml->readElementText(); + } else if (childName == "deckname") { + metadata.name = xml->readElementText(); + } else if (childName == "format") { + metadata.gameFormat = xml->readElementText(); + } else if (childName == "comments") { + metadata.comments = xml->readElementText(); + } else if (childName == "bannerCard") { + QString providerId = xml->attributes().value("providerId").toString(); + QString cardName = xml->readElementText(); + metadata.bannerCard = {cardName, providerId}; + } else if (childName == "playmatCard") { + QString providerId = xml->attributes().value("providerId").toString(); + bool ok; + QString marginLStr = xml->attributes().value("marginPctL").toString(); + QString marginRStr = xml->attributes().value("marginPctR").toString(); + QString vOffStr = xml->attributes().value("verticalOffset").toString(); + QString zoomStr = xml->attributes().value("zoom").toString(); + QString cardName = xml->readElementText(); + PlaymatInfo playmat; + playmat.card = {cardName, providerId}; + // Clamp to the same ranges as the settings dialog and the remote + // player-properties path so malformed deck files cannot produce + // degenerate art rectangles (e.g. a zoom of 0 dividing by zero). + playmat.params.marginPctL = qBound(0.0, marginLStr.toDouble(&ok), 0.95); + if (!ok) { + playmat.params.marginPctL = 0.07; + } + playmat.params.marginPctR = qBound(0.0, marginRStr.toDouble(&ok), 0.95); + if (!ok) { + playmat.params.marginPctR = 0.07; + } + playmat.params.verticalOffset = qBound(0.0, vOffStr.toDouble(&ok), 1.0); + if (!ok) { + playmat.params.verticalOffset = 0.33; + } + playmat.params.zoom = qBound(0.1, zoomStr.toDouble(&ok), 4.0); + if (!ok) { + playmat.params.zoom = 1.0; + } + metadata.playmat = playmat; + } else if (childName == "tags") { + metadata.tags.clear(); // Clear existing tags + while (xml->readNextStartElement()) { + if (xml->name().toString() == "tag") { + metadata.tags.append(xml->readElementText()); + } + } + } else if (childName == "zone") { tree.readZoneElement(xml); } else if (childName == "sideboard_plan") { SideboardPlan newSideboardPlan; @@ -192,12 +125,41 @@ bool DeckList::readElement(QXmlStreamReader *xml) return true; } +static void writeMetadata(QXmlStreamWriter *xml, const DeckList::Metadata &metadata) +{ + xml->writeTextElement("lastLoadedTimestamp", metadata.lastLoadedTimestamp); + xml->writeTextElement("deckname", metadata.name); + xml->writeTextElement("format", metadata.gameFormat); + xml->writeStartElement("bannerCard"); + xml->writeAttribute("providerId", metadata.bannerCard.providerId); + xml->writeCharacters(metadata.bannerCard.name); + xml->writeEndElement(); + if (!metadata.playmat.card.isEmpty()) { + xml->writeStartElement("playmatCard"); + xml->writeAttribute("providerId", metadata.playmat.card.providerId); + xml->writeAttribute("marginPctL", QString::number(metadata.playmat.params.marginPctL, 'f', 4)); + xml->writeAttribute("marginPctR", QString::number(metadata.playmat.params.marginPctR, 'f', 4)); + xml->writeAttribute("verticalOffset", QString::number(metadata.playmat.params.verticalOffset, 'f', 4)); + xml->writeAttribute("zoom", QString::number(metadata.playmat.params.zoom, 'f', 4)); + xml->writeCharacters(metadata.playmat.card.name); + xml->writeEndElement(); + } + xml->writeTextElement("comments", metadata.comments); + + // Write tags + xml->writeStartElement("tags"); + for (const QString &tag : metadata.tags) { + xml->writeTextElement("tag", tag); + } + xml->writeEndElement(); +} + void DeckList::write(QXmlStreamWriter *xml) const { xml->writeStartElement("cockatrice_deck"); xml->writeAttribute("version", "1"); - metadata.write(xml); + writeMetadata(xml, metadata); // Write zones tree.write(xml); @@ -210,27 +172,6 @@ void DeckList::write(QXmlStreamWriter *xml) const xml->writeEndElement(); // Close "cockatrice_deck" } -bool DeckList::seekToNextElement(QXmlStreamReader *xml) -{ - while (!xml->atEnd()) { - xml->readNext(); - if (xml->isStartElement()) { - return true; - } - } - return false; -} - -void DeckList::readDeckBody(QXmlStreamReader *xml) -{ - while (!xml->atEnd()) { - xml->readNext(); - if (!readElement(xml)) { - break; - } - } -} - bool DeckList::loadFromXml(QXmlStreamReader *xml) { if (xml->error()) { @@ -239,11 +180,19 @@ bool DeckList::loadFromXml(QXmlStreamReader *xml) } cleanList(); - while (seekToNextElement(xml)) { - if (xml->name().toString() != "cockatrice_deck") { - return false; + while (!xml->atEnd()) { + xml->readNext(); + if (xml->isStartElement()) { + if (xml->name().toString() != "cockatrice_deck") { + return false; + } + while (!xml->atEnd()) { + xml->readNext(); + if (!readElement(xml)) { + break; + } + } } - readDeckBody(xml); } refreshDeckHash(); if (xml->error()) { @@ -299,12 +248,160 @@ bool DeckList::loadFromStream_Plain(QTextStream &in, bool preserveMetadata, const std::function &cardNameNormalizer) { - if (!preserveMetadata) { - metadata = {}; + const QRegularExpression reCardLine(R"(^\s*[\w\[\(\{].*$)", QRegularExpression::UseUnicodePropertiesOption); + const QRegularExpression reEmpty("^\\s*$"); + const QRegularExpression reComment(R"([\w\[\(\{].*$)", QRegularExpression::UseUnicodePropertiesOption); + const QRegularExpression reSBMark("^\\s*sb:\\s*(.+)", QRegularExpression::CaseInsensitiveOption); + const QRegularExpression reSBComment("^sideboard\\b.*$", QRegularExpression::CaseInsensitiveOption); + const QRegularExpression reDeckComment("^((main)?deck(list)?|mainboard)\\b", + QRegularExpression::CaseInsensitiveOption); + + // Regex for advanced card parsing + const QRegularExpression reMultiplier(R"(^[xX\(\[]*(\d+)[xX\*\)\]]* ?(.+))"); + + // Regex for extracting set code and collector number with attached symbols + const QRegularExpression reHyphenFormat(R"(\((\w{3,})\)\s+(\w{3,})-(\d+[^\w\s]*))"); + const QRegularExpression reRegularFormat(R"(\((\w{3,})\)\s+(\d+[^\w\s]*))"); + + cleanList(preserveMetadata); + + auto inputs = in.readAll().trimmed().split('\n'); + auto max_line = inputs.size(); + + // Start at the first empty line before the first card line + auto deckStart = inputs.indexOf(reCardLine); + if (deckStart == -1) { + if (inputs.indexOf(reComment) == -1) { + return false; // Input is empty + } + deckStart = max_line; + } else { + deckStart = inputs.lastIndexOf(reEmpty, deckStart); + if (deckStart == -1) { + deckStart = 0; + } } - bool ok = DeckListPlainText::parse(in, cardNameNormalizer, metadata, tree); + + // find sideboard position, if marks are used this won't be needed + int sBStart = -1; + if (inputs.indexOf(reSBMark, deckStart) == -1) { + sBStart = inputs.indexOf(reSBComment, deckStart); + if (sBStart == -1) { + sBStart = inputs.indexOf(reEmpty, deckStart + 1); + if (sBStart == -1) { + sBStart = max_line; + } + auto nextCard = inputs.indexOf(reCardLine, sBStart + 1); + if (inputs.indexOf(reEmpty, nextCard + 1) != -1) { + sBStart = max_line; + } + } + } + + int index = 0; + QRegularExpressionMatch match; + + // Parse name and comments + while (index < deckStart) { + const auto ¤t = inputs.at(index++); + if (!current.contains(reEmpty)) { + match = reComment.match(current); + metadata.name = match.captured(); + break; + } + } + while (index < deckStart) { + const auto ¤t = inputs.at(index++); + if (!current.contains(reEmpty)) { + match = reComment.match(current); + metadata.comments += match.captured() + '\n'; + } + } + metadata.comments.chop(1); + + // Discard empty lines + while (index < max_line && inputs.at(index).contains(reEmpty)) { + ++index; + } + + // Discard line if it starts with deck or mainboard, all cards until the sideboard starts are in the mainboard + if (inputs.at(index).contains(reDeckComment)) { + ++index; + } + + // Parse decklist + for (; index < max_line; ++index) { + // check if line is a card + match = reCardLine.match(inputs.at(index)); + if (!match.hasMatch()) { + continue; + } + + QString cardName = match.captured().simplified(); + bool sideboard = false; + + // Sideboard detection + if (sBStart < 0) { + match = reSBMark.match(cardName); + if (match.hasMatch()) { + sideboard = true; + cardName = match.captured(1); + } + } else { + if (index == sBStart) { + continue; + } + sideboard = index > sBStart; + } + + // Extract set code, collector number, and foil + QString setCode; + QString collectorNumber; + bool isFoil = false; + + // Check for foil status at the end of the card name + if (cardName.endsWith("*F*", Qt::CaseInsensitive)) { + isFoil = true; + cardName.chop(3); // Remove the "*F*" from the card name + } + Q_UNUSED(isFoil); + + // Attempt to match the hyphen-separated format (PLST-2094) + match = reHyphenFormat.match(cardName); + if (match.hasMatch()) { + setCode = match.captured(2).toUpper(); + collectorNumber = match.captured(3); + cardName = cardName.left(match.capturedStart()).trimmed(); + } else { + // Attempt to match the regular format (PLST) 2094 + match = reRegularFormat.match(cardName); + if (match.hasMatch()) { + setCode = match.captured(1).toUpper(); + collectorNumber = match.captured(2); + cardName = cardName.left(match.capturedStart()).trimmed(); + } + } + + // check if a specific amount is mentioned + int amount = 1; + match = reMultiplier.match(cardName); + if (match.hasMatch()) { + amount = match.captured(1).toInt(); + cardName = match.captured(2); + } + + // Normalize the card name + cardName = cardNameNormalizer(cardName); + + // Determine the zone (mainboard/sideboard) + QString zoneName = sideboard ? DECK_ZONE_SIDE : DECK_ZONE_MAIN; + + // make new entry in decklist + tree.addCard(cardName, amount, zoneName, -1, setCode, collectorNumber); + } + refreshDeckHash(); - return ok; + return true; } bool DeckList::loadFromFile_Plain(QIODevice *device, const std::function &cardNameNormalizer) diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h index 199d3a9a8..475d99560 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h @@ -76,23 +76,6 @@ public: * @brief Checks if all values (except for lastLoadedTimestamp) in the metadata is empty. */ bool isEmpty() const; - - /** - * @brief Reads a single deck metadata element from a Cockatrice deck XML stream. - * - * @param xml Reader positioned at the element. - * @param childName Name of the current element. - * @return true if a metadata element was consumed, false if @p childName is - * not a metadata element. - */ - bool readElement(QXmlStreamReader *xml, const QString &childName); - - /** - * @brief Writes the deck metadata section of a Cockatrice deck XML file. - * - * @param xml Writer to append the metadata elements to. - */ - void write(QXmlStreamWriter *xml) const; }; private: @@ -106,22 +89,6 @@ private: */ mutable QString cachedDeckHash; - /** @name XML load helpers */ - ///@{ - /** - * @brief Advances to the next element in the XML stream. - * @param xml Reader to advance past non-element tokens. - * @return true when a start element was reached, false at end of stream. - */ - bool seekToNextElement(QXmlStreamReader *xml); - - /** - * @brief Reads the contents of a `cockatrice_deck` element into this deck. - * @param xml Reader positioned at the deck element, stopped at its end. - */ - void readDeckBody(QXmlStreamReader *xml); - ///@} - public: /** @name Metadata setters */ ///@{ diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.cpp index b6d0687b4..acf4707ab 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.cpp @@ -14,32 +14,42 @@ void DeckListHistoryManager::clear() emit undoRedoStateChanged(); } -void DeckListHistoryManager::restoreAndSwap(QStack &source, - QStack &target, - DeckList *deck) +void DeckListHistoryManager::undo(DeckList *deck) { - if (source.isEmpty()) { + if (undoStack.isEmpty()) { return; } - // The reason is read before the source is popped. - const QString reason = source.top().getReason(); + // Peek at the memento we are going to restore + const DeckListMemento &mementoToRestore = undoStack.top(); - // Save the current state so the opposite direction can return to it. - target.push(deck->createMemento(reason)); + // Save current state for redo + DeckListMemento currentState = deck->createMemento(mementoToRestore.getReason()); + redoStack.push(currentState); - // Apply the state we are moving to. - deck->restoreMemento(source.pop()); + // Pop the last state from undo stack and restore it + DeckListMemento memento = undoStack.pop(); + deck->restoreMemento(memento); emit undoRedoStateChanged(); } -void DeckListHistoryManager::undo(DeckList *deck) -{ - restoreAndSwap(undoStack, redoStack, deck); -} - void DeckListHistoryManager::redo(DeckList *deck) { - restoreAndSwap(redoStack, undoStack, deck); + if (redoStack.isEmpty()) { + return; + } + + // Peek at the memento we are going to restore + const DeckListMemento &mementoToRestore = redoStack.top(); + + // Save current state for undo + DeckListMemento currentState = deck->createMemento(mementoToRestore.getReason()); + undoStack.push(currentState); + + // Pop the next state from redo stack and restore it + DeckListMemento memento = redoStack.pop(); + deck->restoreMemento(memento); + + emit undoRedoStateChanged(); } diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.h index 6acdb199e..e6bd27e2d 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.h @@ -47,13 +47,6 @@ public: } private: - /** - * @brief Moves one state from @p source to @p target, applying it to @p deck. - * - * Used by both undo (undoStack -> redoStack) and redo (redoStack -> undoStack). - */ - void restoreAndSwap(QStack &source, QStack &target, DeckList *deck); - QStack undoStack; QStack redoStack; }; diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.cpp index 66f228d19..91fe1874b 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.cpp @@ -7,84 +7,6 @@ static constexpr int MAX_DECK_SIZE = 1e5; -namespace -{ - -/** - * @brief Expands card nodes into one lowercase name entry per copy. - * - * @param nodes The card nodes to expand. - * @param prefix Optional prefix prepended to every entry (e.g. "SB:" for the sideboard). - * @return One entry per copy, in node order. - */ -QStringList cardNodesToCopies(const QList &nodes, const QString &prefix = {}) -{ - QStringList result; - for (auto node : nodes) { - for (int i = 0; i < node->getNumber(); ++i) { - result.append(prefix + node->getName().toLower()); - } - } - return result; -} - -/** - * @brief Packs the first five bytes of a SHA-1 digest into a compact base-32 number. - * - * The bytes are placed in most-significant-byte-first order (byte 0 shifted by 32, - * byte 4 unshifted) so decks that only differ in their low-order hash bytes still - * produce distinct identifiers. - * - * @return The 8-character base-32 representation of the packed number. - */ -QString encodeDeckHash(const QByteArray &digest) -{ - quint64 number = 0; - for (int i = 0; i < 5; ++i) { - number |= static_cast(static_cast(digest[i])) << (32 - 8 * i); - } - return QString::number(number, 32).rightJustified(8, '0'); -} - -/** - * @brief Collects every card node in @p node's subtree, in tree order. - * - * @return The collected card nodes. - */ -QList collectCardsRecursive(const InnerDecklistNode *node) -{ - QList result; - for (int i = 0; i < node->size(); i++) { - if (auto *card = dynamic_cast(node->at(i))) { - result.append(card); - } else if (auto *inner = dynamic_cast(node->at(i))) { - result.append(collectCardsRecursive(inner)); - } - } - return result; -} - -/** - * @brief Invokes @p func on every card in @p node's subtree. - * - * Cards nested in custom zones are reported with their top-level @p boardZone - * so that callers can classify cards by board (main/side/maybeboard/tokens). - */ -void forEachCardInNode(InnerDecklistNode *boardZone, - InnerDecklistNode *node, - const std::function &func) -{ - for (int i = 0; i < node->size(); i++) { - if (auto *card = dynamic_cast(node->at(i))) { - func(boardZone, card); - } else if (auto *inner = dynamic_cast(node->at(i))) { - forEachCardInNode(boardZone, inner, func); - } - } -} - -} // namespace - DecklistNodeTree::DecklistNodeTree() : root(new InnerDecklistNode()) { } @@ -121,8 +43,19 @@ QList DecklistNodeTree::getCardNodes(const QSet result; + std::function collectCards = [&collectCards, + &result](const InnerDecklistNode *node) { + for (int i = 0; i < node->size(); i++) { + if (auto *card = dynamic_cast(node->at(i))) { + result.append(card); + } else if (auto *inner = dynamic_cast(node->at(i))) { + collectCards(inner); + } + } + }; + for (auto *zoneNode : getZoneNodes(restrictToZones)) { - result.append(collectCardsRecursive(zoneNode)); + collectCards(zoneNode); } return result; @@ -150,11 +83,25 @@ QString DecklistNodeTree::computeDeckHash() const auto mainDeckNodes = getCardNodes({DECK_ZONE_MAIN}); auto sideDeckNodes = getCardNodes({DECK_ZONE_SIDE}); - QStringList cardList = cardNodesToCopies(mainDeckNodes) + cardNodesToCopies(sideDeckNodes, "SB:"); + static auto nodesToCardList = [](const QList &nodes, const QString &prefix = {}) { + QStringList result; + for (auto node : nodes) { + for (int i = 0; i < node->getNumber(); ++i) { + result.append(prefix + node->getName().toLower()); + } + } + return result; + }; + + QStringList cardList = nodesToCardList(mainDeckNodes) + nodesToCardList(sideDeckNodes, "SB:"); cardList.sort(); QByteArray deckHashArray = QCryptographicHash::hash(cardList.join(";").toUtf8(), QCryptographicHash::Sha1); - return encodeDeckHash(deckHashArray); + quint64 number = (((quint64)(unsigned char)deckHashArray[0]) << 32) + + (((quint64)(unsigned char)deckHashArray[1]) << 24) + + (((quint64)(unsigned char)deckHashArray[2] << 16)) + + (((quint64)(unsigned char)deckHashArray[3]) << 8) + (quint64)(unsigned char)deckHashArray[4]; + return QString::number(number, 32).rightJustified(8, '0'); } void DecklistNodeTree::write(QXmlStreamWriter *xml) const @@ -201,7 +148,12 @@ bool DecklistNodeTree::deleteNode(AbstractDecklistNode *node, InnerDecklistNode int index = rootNode->indexOf(node); if (index != -1) { delete rootNode->takeAt(index); - pruneEmptyBoardZone(rootNode); + + // Empty custom zones are kept while empty board zones get pruned. + if (rootNode->empty() && rootNode->getParent() == root) { + deleteNode(rootNode, rootNode->getParent()); + } + return true; } @@ -217,18 +169,24 @@ bool DecklistNodeTree::deleteNode(AbstractDecklistNode *node, InnerDecklistNode return false; } -void DecklistNodeTree::pruneEmptyBoardZone(InnerDecklistNode *container) -{ - if (container->isEmpty() && container->getParent() == root) { - deleteNode(container, container->getParent()); - } -} - void DecklistNodeTree::forEachCard(const std::function &func) const { + // Cards nested in custom zones are reported with their top-level board zone + // so that callers can classify cards by board (main/side/maybeboard/tokens). + std::function walk = [&func, &walk](InnerDecklistNode *boardZone, + InnerDecklistNode *node) { + for (int i = 0; i < node->size(); i++) { + if (auto *card = dynamic_cast(node->at(i))) { + func(boardZone, card); + } else if (auto *inner = dynamic_cast(node->at(i))) { + walk(boardZone, inner); + } + } + }; + for (int i = 0; i < root->size(); i++) { if (auto *zone = dynamic_cast(root->at(i))) { - forEachCardInNode(zone, zone, func); + walk(zone, zone); } } } @@ -239,7 +197,7 @@ void DecklistNodeTree::forEachCard(const std::functionsize(); i++) { auto *node = dynamic_cast(root->at(i)); diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.h index 2c66b34ff..5d91cd233 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.h @@ -144,17 +144,9 @@ public: private: // Helpers for traversing the tree - InnerDecklistNode *getZoneObjFromName(const QString &zoneName); + InnerDecklistNode *getZoneObjFromName(const QString &zoneName) const; InnerDecklistNode *findBoardZone(const QString &boardZoneName) const; InnerDecklistNode *findOrCreateBoardZone(const QString &boardZoneName); - - /** - * @brief Recursively removes @p container when it is an empty board zone. - * - * Empty custom zones are kept while empty board zones get pruned, so a - * board zone disappears once its last card or custom zone goes away. - */ - void pruneEmptyBoardZone(InnerDecklistNode *container); }; #endif // COCKATRICE_DECKLIST_NODE_TREE_H diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_plain_text_parser.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_plain_text_parser.cpp deleted file mode 100644 index de50d743b..000000000 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_plain_text_parser.cpp +++ /dev/null @@ -1,172 +0,0 @@ -#include "deck_list_plain_text_parser.h" - -#include "deck_list_node_tree.h" -#include "tree/inner_deck_list_node.h" - -#include -#include - -namespace DeckListPlainText -{ - -bool parse(QTextStream &in, - const std::function &cardNameNormalizer, - DeckList::Metadata &metadata, - DecklistNodeTree &tree) -{ - tree.clear(); - - static const QRegularExpression reCardLine(R"(^\s*[\w\[\(\{].*$)", QRegularExpression::UseUnicodePropertiesOption); - static const QRegularExpression reEmpty("^\\s*$"); - static const QRegularExpression reComment(R"([\w\[\(\{].*$)", QRegularExpression::UseUnicodePropertiesOption); - static const QRegularExpression reSBMark("^\\s*sb:\\s*(.+)", QRegularExpression::CaseInsensitiveOption); - static const QRegularExpression reSBComment("^sideboard\\b.*$", QRegularExpression::CaseInsensitiveOption); - static const QRegularExpression reDeckComment("^((main)?deck(list)?|mainboard)\\b", - QRegularExpression::CaseInsensitiveOption); - - // Regex for advanced card parsing - static const QRegularExpression reMultiplier(R"(^[xX\(\[]*(\d+)[xX\*\)\]]* ?(.+))"); - - // Regex for extracting set code and collector number with attached symbols - static const QRegularExpression reHyphenFormat(R"(\((\w{3,})\)\s+(\w{3,})-(\d+[^\w\s]*))"); - static const QRegularExpression reRegularFormat(R"(\((\w{3,})\)\s+(\d+[^\w\s]*))"); - - auto inputs = in.readAll().trimmed().split('\n'); - auto max_line = inputs.size(); - - // Start at the first empty line before the first card line - auto deckStart = inputs.indexOf(reCardLine); - if (deckStart == -1) { - if (inputs.indexOf(reComment) == -1) { - return false; // Input is empty - } - deckStart = max_line; - } else { - deckStart = inputs.lastIndexOf(reEmpty, deckStart); - if (deckStart == -1) { - deckStart = 0; - } - } - - // find sideboard position, if marks are used this won't be needed - int sBStart = -1; - if (inputs.indexOf(reSBMark, deckStart) == -1) { - sBStart = inputs.indexOf(reSBComment, deckStart); - if (sBStart == -1) { - sBStart = inputs.indexOf(reEmpty, deckStart + 1); - if (sBStart == -1) { - sBStart = max_line; - } - auto nextCard = inputs.indexOf(reCardLine, sBStart + 1); - if (inputs.indexOf(reEmpty, nextCard + 1) != -1) { - sBStart = max_line; - } - } - } - - int index = 0; - QRegularExpressionMatch match; - - // Parse name and comments - while (index < deckStart) { - const auto ¤t = inputs.at(index++); - if (!current.contains(reEmpty)) { - match = reComment.match(current); - metadata.name = match.captured(); - break; - } - } - while (index < deckStart) { - const auto ¤t = inputs.at(index++); - if (!current.contains(reEmpty)) { - match = reComment.match(current); - metadata.comments += match.captured() + '\n'; - } - } - metadata.comments.chop(1); - - // Discard empty lines - while (index < max_line && inputs.at(index).contains(reEmpty)) { - ++index; - } - - // Discard line if it starts with deck or mainboard, all cards until the sideboard starts are in the mainboard - if (inputs.at(index).contains(reDeckComment)) { - ++index; - } - - // Parse decklist - for (; index < max_line; ++index) { - // check if line is a card - match = reCardLine.match(inputs.at(index)); - if (!match.hasMatch()) { - continue; - } - - QString cardName = match.captured().simplified(); - bool sideboard = false; - - // Sideboard detection - if (sBStart < 0) { - match = reSBMark.match(cardName); - if (match.hasMatch()) { - sideboard = true; - cardName = match.captured(1); - } - } else { - if (index == sBStart) { - continue; - } - sideboard = index > sBStart; - } - - // Extract set code, collector number, and foil - QString setCode; - QString collectorNumber; - bool isFoil = false; - - // Check for foil status at the end of the card name - if (cardName.endsWith("*F*", Qt::CaseInsensitive)) { - isFoil = true; - cardName.chop(3); // Remove the "*F*" from the card name - } - Q_UNUSED(isFoil); - - // Attempt to match the hyphen-separated format (PLST-2094) - match = reHyphenFormat.match(cardName); - if (match.hasMatch()) { - setCode = match.captured(2).toUpper(); - collectorNumber = match.captured(3); - cardName = cardName.left(match.capturedStart()).trimmed(); - } else { - // Attempt to match the regular format (PLST) 2094 - match = reRegularFormat.match(cardName); - if (match.hasMatch()) { - setCode = match.captured(1).toUpper(); - collectorNumber = match.captured(2); - cardName = cardName.left(match.capturedStart()).trimmed(); - } - } - - // check if a specific amount is mentioned - int amount = 1; - match = reMultiplier.match(cardName); - if (match.hasMatch()) { - amount = match.captured(1).toInt(); - cardName = match.captured(2); - } - - // Normalize the card name - cardName = cardNameNormalizer(cardName); - - // Determine the zone (mainboard/sideboard) - QString zoneName = sideboard ? DECK_ZONE_SIDE : DECK_ZONE_MAIN; - - // make new entry in decklist - tree.addCard(cardName, amount, zoneName, -1, setCode, collectorNumber); - } - - return true; -} - -} // namespace DeckListPlainText \ No newline at end of file diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_plain_text_parser.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_plain_text_parser.h deleted file mode 100644 index e0f456a18..000000000 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_plain_text_parser.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef COCKATRICE_DECK_LIST_PLAIN_TEXT_PARSER_H -#define COCKATRICE_DECK_LIST_PLAIN_TEXT_PARSER_H - -#include "deck_list.h" - -#include -#include - -class QTextStream; - -namespace DeckListPlainText -{ - -/** - * @brief Parses a plain-text deck list into a tree and its metadata. - * - * Clears the tree first, then fills both from the text. - * - * @param in The text to load - * @param cardNameNormalizer Function that takes the parsed card name string - * in the text and returns the name to store - * @param metadata Deck metadata written by the parser - * @param tree Deck tree the parser adds cards to - * @return False if the input was empty, true otherwise. - */ -bool parse(QTextStream &in, - const std::function &cardNameNormalizer, - DeckList::Metadata &metadata, - DecklistNodeTree &tree); - -} // namespace DeckListPlainText - -#endif // COCKATRICE_DECK_LIST_PLAIN_TEXT_PARSER_H \ No newline at end of file diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.cpp index 855062c18..a76fed619 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.cpp @@ -2,32 +2,6 @@ #include -namespace -{ - -void readMoveCardToZone(QXmlStreamReader *xml, QList &moveList) -{ - MoveCard_ToZone move; - while (!xml->atEnd()) { - xml->readNext(); - const QString childName = xml->name().toString(); - if (xml->isStartElement()) { - if (childName == "card_name") { - move.set_card_name(xml->readElementText().toStdString()); - } else if (childName == "start_zone") { - move.set_start_zone(xml->readElementText().toStdString()); - } else if (childName == "target_zone") { - move.set_target_zone(xml->readElementText().toStdString()); - } - } else if (xml->isEndElement() && (childName == "move_card_to_zone")) { - moveList.append(move); - return; - } - } -} - -} // namespace - SideboardPlan::SideboardPlan(const QString &_name, const QList &_moveList) : name(_name), moveList(_moveList) { @@ -47,7 +21,23 @@ bool SideboardPlan::readElement(QXmlStreamReader *xml) if (childName == "name") { name = xml->readElementText(); } else if (childName == "move_card_to_zone") { - readMoveCardToZone(xml, moveList); + MoveCard_ToZone m; + while (!xml->atEnd()) { + xml->readNext(); + const QString childName2 = xml->name().toString(); + if (xml->isStartElement()) { + if (childName2 == "card_name") { + m.set_card_name(xml->readElementText().toStdString()); + } else if (childName2 == "start_zone") { + m.set_start_zone(xml->readElementText().toStdString()); + } else if (childName2 == "target_zone") { + m.set_target_zone(xml->readElementText().toStdString()); + } + } else if (xml->isEndElement() && (childName2 == "move_card_to_zone")) { + moveList.append(m); + break; + } + } } } else if (xml->isEndElement() && (childName == "sideboard_plan")) { return true; diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.cpp index 685238d53..7200ede5f 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.cpp @@ -34,6 +34,17 @@ bool AbstractDecklistCardNode::compareName(AbstractDecklistNode *other) const } } +int AbstractDecklistCardNode::readElement(QXmlStreamReader *xml, int /* limit */) +{ + while (!xml->atEnd()) { + xml->readNext(); + if (xml->isEndElement() && xml->name().toString() == "card") { + return 0; + } + } + return 0; +} + void AbstractDecklistCardNode::writeElement(QXmlStreamWriter *xml) { xml->writeEmptyElement("card"); diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.h b/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.h index 0942a4601..52dd56529 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.h @@ -134,6 +134,15 @@ public: */ bool compareName(AbstractDecklistNode *other) const; + /** + * @brief Deserialize this node’s properties from XML. + * @param xml QXmlStreamReader positioned at the element. + * @return true if parsing succeeded. + * + * This supports loading deck files from Cockatrice’s XML format. + */ + int readElement(QXmlStreamReader *xml, int limit) override; + /** * @brief Serialize this node’s properties to XML. * @param xml Writer to append this node’s XML element. diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_node.h b/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_node.h index 38d1d44b8..9c4290db0 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_node.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_node.h @@ -179,10 +179,11 @@ public: /** * @name XML serialization - * This method supports writing this node and its children to the + * These methods support reading and writing decks from/to * Cockatrice deck XML format. * @{ */ + virtual int readElement(QXmlStreamReader *xml, int limit) = 0; virtual void writeElement(QXmlStreamWriter *xml) = 0; /// @} }; diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/tree/inner_deck_list_node.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/tree/inner_deck_list_node.cpp index 86f7f5363..d082b3cca 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/tree/inner_deck_list_node.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/tree/inner_deck_list_node.cpp @@ -151,29 +151,25 @@ bool InnerDecklistNode::compareName(AbstractDecklistNode *other) const } } -int InnerDecklistNode::readCardElement(QXmlStreamReader *xml, int remainingBudget) -{ - const int amount = qMin(xml->attributes().value("number").toString().toInt(), remainingBudget); - new DecklistCardNode(xml->attributes().value("name").toString(), amount, this, -1, - xml->attributes().value("setShortName").toString(), - xml->attributes().value("collectorNumber").toString(), - xml->attributes().value("uuid").toString()); - return amount; -} - int InnerDecklistNode::readElement(QXmlStreamReader *xml, int limit) { int totalCards = 0; while (!xml->atEnd()) { xml->readNext(); const QString childName = xml->name().toString(); - const int remainingBudget = limit - totalCards; if (xml->isStartElement()) { if (childName == "zone") { auto *newZone = new InnerDecklistNode(xml->attributes().value("name").toString(), this); - totalCards += newZone->readElement(xml, remainingBudget); + totalCards += newZone->readElement(xml, limit - totalCards); } else if (childName == "card") { - totalCards += readCardElement(xml, remainingBudget); + int amount = xml->attributes().value("number").toString().toInt(); + amount = qMin(amount, limit - totalCards); + auto *newCard = new DecklistCardNode(xml->attributes().value("name").toString(), amount, this, -1, + xml->attributes().value("setShortName").toString(), + xml->attributes().value("collectorNumber").toString(), + xml->attributes().value("uuid").toString()); + totalCards += amount; + totalCards += newCard->readElement(xml, limit - totalCards); } } else if (xml->isEndElement() && (childName == "zone")) { return totalCards; @@ -192,35 +188,31 @@ void InnerDecklistNode::writeElement(QXmlStreamWriter *xml) xml->writeEndElement(); // zone } -QVector> InnerDecklistNode::indexedSnapshot() const -{ - QVector> snapshot(size()); - for (int i = size() - 1; i >= 0; --i) { - snapshot[i].first = i; - snapshot[i].second = at(i); - } - return snapshot; -} - -QVector> InnerDecklistNode::applySortedOrder(const QVector> &sorted) -{ - QVector> result(size()); - for (int i = size() - 1; i >= 0; --i) { - result[i].first = sorted[i].first; - result[i].second = i; - replace(i, sorted[i].second); - } - return result; -} - QVector> InnerDecklistNode::sort(Qt::SortOrder order) { - auto snapshot = indexedSnapshot(); + QVector> result(size()); + // Initialize temporary list with contents of current list + QVector> tempList(size()); + for (int i = size() - 1; i >= 0; --i) { + tempList[i].first = i; + tempList[i].second = at(i); + } + + // Sort temporary list auto cmp = [order](const auto &a, const auto &b) { return (order == Qt::AscendingOrder) ? (b.second->compare(a.second)) : (a.second->compare(b.second)); }; - std::sort(snapshot.begin(), snapshot.end(), cmp); - return applySortedOrder(snapshot); + std::sort(tempList.begin(), tempList.end(), cmp); + + // Map old indexes to new indexes and + // copy temporary list to the current one + for (int i = size() - 1; i >= 0; --i) { + result[i].first = tempList[i].first; + result[i].second = i; + replace(i, tempList[i].second); + } + + return result; } diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/tree/inner_deck_list_node.h b/libcockatrice_deck_list/libcockatrice/deck_list/tree/inner_deck_list_node.h index 8404d7116..0d454c11e 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/tree/inner_deck_list_node.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/tree/inner_deck_list_node.h @@ -223,45 +223,19 @@ public: */ QVector> sort(Qt::SortOrder order = Qt::AscendingOrder); -private: - /** - * @brief Snapshots the current children as (old index, node) pairs. - */ - QVector> indexedSnapshot() const; - - /** - * @brief Replaces this node's children with @p sorted and maps old indexes to new ones. - * - * @return A list of (old index, new index) pairs for each reordered child. - */ - QVector> applySortedOrder(const QVector> &sorted); - -public: /** * @brief Deserialize this node and its children from XML. * @param xml Reader positioned at this element. * @param limit The maximum amount of cards to read * @return the amount of cards found */ - int readElement(QXmlStreamReader *xml, int limit); + int readElement(QXmlStreamReader *xml, int limit) override; /** * @brief Serialize this node and its children to XML. * @param xml Writer to append elements to. */ void writeElement(QXmlStreamWriter *xml) override; - -private: - /** - * @brief Reads a single `card` element and appends it to this node. - * - * The card's quantity is capped at @p remainingBudget so a malicious or - * oversized deck file cannot push the total card count past the deck size - * limit. - * - * @return The amount of cards actually added. - */ - int readCardElement(QXmlStreamReader *xml, int remainingBudget); }; #endif // COCKATRICE_INNER_DECK_LIST_NODE_H diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/interface_cards_display_settings_provider.h b/libcockatrice_interfaces/libcockatrice/interfaces/interface_cards_display_settings_provider.h index 304ab3cb7..3f2cbbe8e 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/interface_cards_display_settings_provider.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/interface_cards_display_settings_provider.h @@ -1,8 +1,6 @@ #ifndef COCKATRICE_INTERFACE_CARDS_DISPLAY_SETTINGS_PROVIDER_H #define COCKATRICE_INTERFACE_CARDS_DISPLAY_SETTINGS_PROVIDER_H -#include - class ICardsDisplaySettingsProvider { public: @@ -28,7 +26,6 @@ public: [[nodiscard]] virtual int getEDHRecCardSize() const = 0; [[nodiscard]] virtual int getArchidektPreviewSize() const = 0; [[nodiscard]] virtual int getSampleHandSize() const = 0; - [[nodiscard]] virtual QString getCardLang() const = 0; }; #endif // COCKATRICE_INTERFACE_CARDS_DISPLAY_SETTINGS_PROVIDER_H diff --git a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp index a5628a844..76afca0c4 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -28,16 +28,6 @@ DeckListModel::~DeckListModel() delete root; } -void DeckListModel::setDisplayLanguage(const QString &lang) -{ - if (displayLang == lang) { - return; - } - displayLang = lang; - emit layoutAboutToBeChanged(); - emit layoutChanged(); -} - /** * @brief Extract the value from the card that is used for the group criteria. * @param info Pointer to card information. @@ -191,15 +181,8 @@ QVariant DeckListModel::data(const QModelIndex &index, int role) const switch (index.column()) { case DeckListModelColumns::CARD_AMOUNT: return card->getNumber(); - case DeckListModelColumns::CARD_NAME: { - if (role == Qt::DisplayRole) { - CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(card->getName()); - if (info) { - return info->getLocalizedName(displayLang); - } - } + case DeckListModelColumns::CARD_NAME: return card->getName(); - } case DeckListModelColumns::CARD_SET: return card->getCardSetShortName(); case DeckListModelColumns::CARD_COLLECTOR_NUMBER: diff --git a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h index ce6d7f8cb..09600ca67 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h @@ -287,16 +287,6 @@ public: explicit DeckListModel(QObject *parent, const QSharedPointer &deckList); ~DeckListModel() override; - /** - * @brief Selects the language code for localized card names in the display role. - * - * The model never reads global settings itself; callers wire this to the card - * language setting (including reacting to its changes) rather than the model - * querying it. - * @param lang Language code; "en" shows the canonical English names. - */ - void setDisplayLanguage(const QString &lang); - /** * @brief Returns the root index of the model. * @return QModelIndex representing the root node. @@ -418,7 +408,6 @@ private: DeckListModelGroupCriteria::Type activeGroupCriteria = DeckListModelGroupCriteria::MAIN_TYPE; int lastKnownColumn; /**< Last column used for sorting. */ Qt::SortOrder lastKnownOrder; /**< Last known sort order. */ - QString displayLang = "en"; /**< Language code for localized card names in the display role. */ InnerDecklistNode *createNodeIfNeeded(const QString &name, InnerDecklistNode *parent); QModelIndex nodeToIndex(AbstractDecklistNode *node) const; diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp index 131ff1077..799b1e7ee 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp @@ -465,7 +465,7 @@ Response::ResponseCode Server_Game::checkJoin(ServerInfo_User *user, if (asJudge && !(user->user_level() & ServerInfo_User::IsJudge)) { return Response::RespUserLevelTooLow; } - if (!(overrideRestrictions && (user->user_level() & (ServerInfo_User::IsModerator | ServerInfo_User::IsJudge)))) { + if (!(overrideRestrictions && (user->user_level() & ServerInfo_User::IsModerator))) { if ((_password != password) && !(spectator && !spectatorsNeedPassword)) { return Response::RespWrongPassword; } diff --git a/libcockatrice_settings/libcockatrice/settings/appearance_settings.cpp b/libcockatrice_settings/libcockatrice/settings/appearance_settings.cpp index 839e33be4..2f19d6224 100644 --- a/libcockatrice_settings/libcockatrice/settings/appearance_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/appearance_settings.cpp @@ -70,17 +70,6 @@ void AppearanceSettings::setHomeTabDisplayCardName(bool _displayCardName) emit homeTabDisplayCardNameChanged(); } -bool AppearanceSettings::getHomeTabBackgroundDim() const -{ - return getValue("homeTabBackgroundDim", QString(), QString(), true).toBool(); -} - -void AppearanceSettings::setHomeTabBackgroundDim(bool _dimBackground) -{ - setValue(_dimBackground, "homeTabBackgroundDim"); - emit homeTabBackgroundDimChanged(); -} - int AppearanceSettings::getHomeTabButtonColorSourceIndex() const { return getValue("homeTabButtonColorSource", "", "", 0).toInt(); diff --git a/libcockatrice_settings/libcockatrice/settings/appearance_settings.h b/libcockatrice_settings/libcockatrice/settings/appearance_settings.h index a4504798e..3a63f0df0 100644 --- a/libcockatrice_settings/libcockatrice/settings/appearance_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/appearance_settings.h @@ -27,8 +27,6 @@ public: void setHomeTabBackgroundShuffleFrequency(int _frequency); [[nodiscard]] bool getHomeTabDisplayCardName() const; void setHomeTabDisplayCardName(bool _displayCardName); - [[nodiscard]] bool getHomeTabBackgroundDim() const; - void setHomeTabBackgroundDim(bool _dimBackground); [[nodiscard]] int getHomeTabButtonColorSourceIndex() const; void setHomeTabButtonColorSourceIndex(int index); @@ -38,7 +36,6 @@ signals: void homeTabBackgroundSourceChanged(); void homeTabBackgroundShuffleFrequencyChanged(); void homeTabDisplayCardNameChanged(); - void homeTabBackgroundDimChanged(); void homeTabButtonColorChanged(); public: diff --git a/libcockatrice_settings/libcockatrice/settings/cards_display_settings.cpp b/libcockatrice_settings/libcockatrice/settings/cards_display_settings.cpp index b8eafca6c..f528a7c4b 100644 --- a/libcockatrice_settings/libcockatrice/settings/cards_display_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/cards_display_settings.cpp @@ -105,11 +105,6 @@ int CardsDisplaySettings::getSampleHandSize() const return getValue("sampleHandSize", "cards", "cardSize", 7).toInt(); } -QString CardsDisplaySettings::getCardLang() const -{ - return getValue("cardLang", QString(), QString(), "en").toString(); -} - void CardsDisplaySettings::setDisplayCardNames(bool _displayCardNames) { setValue(_displayCardNames, "displayCardNames"); @@ -229,16 +224,3 @@ void CardsDisplaySettings::setSampleHandSize(int _sampleHandSize) setValue(_sampleHandSize, "sampleHandSize", "cards", "cardSize"); emit sampleHandSizeChanged(_sampleHandSize); } - -void CardsDisplaySettings::setCardLang(const QString &_cardLang) -{ - if (_cardLang == getCardLang()) { - return; - } - setValue(_cardLang, "cardLang"); - // Flush to disk immediately: the Oracle tool is a separate process that - // reads this value to decide which foreignData to import, so it must not - // observe a stale (pre-change) value. - sync(); - emit cardLangChanged(_cardLang); -} diff --git a/libcockatrice_settings/libcockatrice/settings/cards_display_settings.h b/libcockatrice_settings/libcockatrice/settings/cards_display_settings.h index 0b47ce490..dbafa32ae 100644 --- a/libcockatrice_settings/libcockatrice/settings/cards_display_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/cards_display_settings.h @@ -31,7 +31,6 @@ public: [[nodiscard]] int getEDHRecCardSize() const override; [[nodiscard]] int getArchidektPreviewSize() const override; [[nodiscard]] int getSampleHandSize() const override; - [[nodiscard]] QString getCardLang() const override; void setDisplayCardNames(bool _displayCardNames); void setRoundCardCorners(bool _roundCardCorners); @@ -53,7 +52,6 @@ public: void setEDHRecCardSize(int _edhrecCardSize); void setArchidektPreviewCardSize(int _archidektPreviewCardSize); void setSampleHandSize(int _sampleHandSize); - void setCardLang(const QString &_cardLang); signals: void displayCardNamesChanged(); @@ -70,7 +68,6 @@ signals: void edhRecCardSizeChanged(); void archidektPreviewSizeChanged(); void sampleHandSizeChanged(int amount); - void cardLangChanged(const QString &lang); public: explicit CardsDisplaySettings(const QString &settingPath, QObject *parent = nullptr); diff --git a/libcockatrice_settings/libcockatrice/settings/download_settings.cpp b/libcockatrice_settings/libcockatrice/settings/download_settings.cpp index eb73e58ee..cfa1c054e 100644 --- a/libcockatrice_settings/libcockatrice/settings/download_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/download_settings.cpp @@ -4,14 +4,11 @@ const QStringList DownloadSettings::DEFAULT_DOWNLOAD_URLS = { "https://cards.scryfall.io/large/!prop:side!/!set:uuid_substr_0_1!/!set:uuid_substr_1_1!/!set:uuid!.jpg", - "https://api.scryfall.com/cards/!set:uuid!?format=image&face=!prop:side!&lang=!sflang!", - "https://api.scryfall.com/cards/multiverse/!set:muid!?format=image&lang=!sflang!", + "https://api.scryfall.com/cards/!set:uuid!?format=image&face=!prop:side!", + "https://api.scryfall.com/cards/multiverse/!set:muid!?format=image", "https://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!set:muid!&type=card", "https://gatherer.wizards.com/Handlers/Image.ashx?name=!name!&type=card"}; -const QString DownloadSettings::SCRYFALL_NAMED_LOCALIZED_URL = - "https://api.scryfall.com/cards/named?fuzzy=!localizedName!&lang=!sflang!&format=image&face=!prop:side!"; - DownloadSettings::DownloadSettings(const QString &settingPath, QObject *parent = nullptr) : SettingsManager(settingPath + "downloads.ini", "downloads", QString(), parent) { @@ -32,18 +29,6 @@ void DownloadSettings::resetToDefaultURLs() setValue(QVariant::fromValue(DEFAULT_DOWNLOAD_URLS), "urls"); } -bool DownloadSettings::addLocalizedScryfallUrl() -{ - const QStringList urls = getAllURLs(); - if (urls.contains(SCRYFALL_NAMED_LOCALIZED_URL)) { - return false; - } - QStringList updated = urls; - updated.prepend(SCRYFALL_NAMED_LOCALIZED_URL); - setDownloadUrls(updated); - return true; -} - bool DownloadSettings::getPicDownload() const { return getValue("pictureDownload", QString(), QString(), true).toBool(); diff --git a/libcockatrice_settings/libcockatrice/settings/download_settings.h b/libcockatrice_settings/libcockatrice/settings/download_settings.h index ae49884f0..a3a6f4ca9 100644 --- a/libcockatrice_settings/libcockatrice/settings/download_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/download_settings.h @@ -15,7 +15,6 @@ class DownloadSettings : public SettingsManager friend class SettingsCache; static const QStringList DEFAULT_DOWNLOAD_URLS; - static const QString SCRYFALL_NAMED_LOCALIZED_URL; public: explicit DownloadSettings(const QString &, QObject *); @@ -23,7 +22,6 @@ public: QStringList getAllURLs() const; void setDownloadUrls(const QStringList &downloadURLs); void resetToDefaultURLs(); - [[nodiscard]] bool addLocalizedScryfallUrl(); [[nodiscard]] bool getPicDownload() const; void setPicDownload(bool _picDownload); [[nodiscard]] bool getDownloadSpoilersStatus() const; diff --git a/libcockatrice_settings/libcockatrice/settings/settings_migration.cpp b/libcockatrice_settings/libcockatrice/settings/settings_migration.cpp index f6e107dad..37dc9a0a0 100644 --- a/libcockatrice_settings/libcockatrice/settings/settings_migration.cpp +++ b/libcockatrice_settings/libcockatrice/settings/settings_migration.cpp @@ -374,11 +374,7 @@ static void migrateAppearanceSettings(const QString &settingsPath, QSettings &gl QSettings appearanceIni(settingsPath + "appearance.ini", QSettings::IniFormat); for (auto it = appearanceKeyMap.constBegin(); it != appearanceKeyMap.constEnd(); ++it) { if (globalIni.contains(it.key())) { - QVariant value = globalIni.value(it.key()); - if (it.key() == "theme/name" && value.toString() == "Default") { - value = "System"; - } - appearanceIni.setValue(it.value(), value); + appearanceIni.setValue(it.value(), globalIni.value(it.key())); } } } diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index 88b522197..fdf11ef02 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include @@ -23,9 +22,8 @@ static const QList kSingletonCounts = {{1, "legal"}, {0, "banned"} SplitCardPart::SplitCardPart(const QString &_name, const QString &_text, const QHash &_properties, - const PrintingInfo &_printingInfo, - const QString &_localizedText) - : name(_name), text(_text), localizedText(_localizedText), properties(_properties), printingInfo(_printingInfo) + const PrintingInfo &_printingInfo) + : name(_name), text(_text), properties(_properties), printingInfo(_printingInfo) { } @@ -35,42 +33,6 @@ OracleImporter::OracleImporter(QObject *parent) : QObject(parent) { } -void OracleImporter::setCardLang(const QString &lang) -{ - cardLang = lang.trimmed().toLower(); - localizationEnabled = cardLang != "en" && CardLocalization::supportedLanguages().contains(cardLang); -} - -/** - * @brief Maps the MTGJSON foreignData language names to the short codes used by - * Scryfall and stored in cards.xml (e.g. "German" -> "de"). - * @param language The language name found in the MTGJSON foreignData entries. - * @return The short language code, or an empty string if unknown. - */ -static QString mtgjsonLanguageToCode(const QString &language) -{ - static const QHash map = { - {"Chinese Simplified", "zhs"}, - {"Chinese Traditional", "zht"}, - {"English", "en"}, - {"French", "fr"}, - {"German", "de"}, - {"Greek", "grc"}, - {"Ancient Greek", "grc"}, - {"Hebrew", "he"}, - {"Italian", "it"}, - {"Japanese", "ja"}, - {"Korean", "ko"}, - {"Latin", "la"}, - {"Phyrexian", "ph"}, - {"Portuguese (Brazil)", "pt"}, - {"Russian", "ru"}, - {"Sanskrit", "sa"}, - {"Spanish", "es"}, - }; - return map.value(language); -} - static CardSet::Priority getSetPriority(const QString &setType, const QString &shortName) { if (!setTypePriorities.contains(setType.toLower())) { @@ -292,101 +254,6 @@ static QString getJsonString(const QJsonObject &obj, const QString &key) return obj.value(key).toVariant().toString(); } -static QString normalizeCardName(QString name) -{ - // Mirror of the name cleanup applied in addCard(), so collected localization - // keys line up with the card map keys (Æ → AE, curly apostrophe → straight). - name = name.replace("Æ", "AE"); - name = name.replace("’", "'"); - return name; -} - -static QString matchingForeignEntryText(const QJsonObject &card, const QString &cardLang) -{ - // Multi-face cards (split/aftermath/adventure/prepare) expose each face as a - // separate card object, each with its own foreignData entry carrying that - // face's rules text; single-face cards carry the full text in one entry. - const QJsonArray foreignData = card.value("foreignData").toArray(); - for (const QJsonValue &entryValue : foreignData) { - const QJsonObject entry = entryValue.toObject(); - // MTGJSON reports languages by long-form name ("German"); match the - // short code ("de") that Scryfall and cards.xml use. - if (mtgjsonLanguageToCode(getJsonString(entry, "language")) == cardLang) { - return getJsonString(entry, "text"); - } - } - return QString(); -} - -void OracleImporter::collectForeignData(const QString &cardKey, - const CardSetPtr ¤tSet, - const QJsonObject &card, - bool collectText) -{ - if (!localizationEnabled) { - return; - } - - LocalizedCardEntry incoming; - bool found = false; - const QJsonArray foreignData = card.value("foreignData").toArray(); - for (const QJsonValue &entryValue : foreignData) { - const QJsonObject entry = entryValue.toObject(); - // MTGJSON reports languages by long-form name ("German"); match the - // short code ("de") that Scryfall and cards.xml use. - if (mtgjsonLanguageToCode(getJsonString(entry, "language")) != cardLang) { - continue; - } - incoming.name = getJsonString(entry, "name"); - incoming.text = collectText ? getJsonString(entry, "text") : QString(); - found = true; - break; - } - if (!found) { - return; - } - - // Prefer the entry from the highest-priority set (lower enum value = more - // authoritative); printings of equal priority keep the first one seen. - incoming.priority = currentSet->getPriority(); - const auto existing = localizedEntries.constFind(cardKey); - if (existing == localizedEntries.constEnd() || incoming.priority < existing->priority) { - localizedEntries.insert(cardKey, incoming); - } -} - -void OracleImporter::applyLocalizedData() -{ - if (!localizationEnabled) { - return; - } - for (auto it = localizedEntries.constBegin(); it != localizedEntries.constEnd(); ++it) { - CardInfoPtr card = cards.value(it.key()); - if (card.isNull()) { - continue; - } - const LocalizedCardEntry &entry = it.value(); - if (!entry.name.isEmpty()) { - card->setLocalizedName(cardLang, entry.name); - } - if (!entry.text.isEmpty()) { - card->setLocalizedText(cardLang, entry.text); - } - } - localizedEntries.clear(); - - for (auto it = splitLocalizedTexts.constBegin(); it != splitLocalizedTexts.constEnd(); ++it) { - CardInfoPtr card = cards.value(it.key()); - if (card.isNull()) { - continue; - } - if (!it.value().text.isEmpty()) { - card->setLocalizedText(cardLang, it.value().text); - } - } - splitLocalizedTexts.clear(); -} - int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QJsonArray &cardsList) { // mtgjson name => xml name @@ -530,21 +397,13 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QJson // split cards are considered a single card, enqueue for later merging if (layout == "split" || layout == "aftermath" || layout == "adventure" || layout == "prepare") { auto _faceName = getJsonString(card, "faceName"); - // MTGJSON exposes each face as a separate card object, each with its - // own foreignData entry holding that face's rules text; collect it so - // the per-face texts can be joined the same way as the English text. - const QString faceLocalizedText = - localizationEnabled ? matchingForeignEntryText(card, cardLang) : QString(); - SplitCardPart split(_faceName, text, properties, printingInfo, faceLocalizedText); + SplitCardPart split(_faceName, text, properties, printingInfo); auto found_iter = splitCards.find(name + numProperty); if (found_iter == splitCards.end()) { splitCards.insert(name + numProperty, {{split}, name}); } else { found_iter->first.append(split); } - // MTGJSON's foreignData name is the joined name present on every - // face, so collect the name once. - collectForeignData(normalizeCardName(name), currentSet, card, false); } else { // relations QList relatedCards; @@ -587,8 +446,6 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QJson } } - collectForeignData(normalizeCardName(name + numComponent), currentSet, card); - CardInfoPtr newCard = addCard(name + numComponent, text, isToken, std::move(properties), relatedCards, printingInfo); numCards++; @@ -602,8 +459,6 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QJson QList, QString>> partsAndNames = splitCards.values(); for (auto [splitCardParts, name] : partsAndNames) { QString text; - QString localizedText; - bool localizedTextComplete = true; QHash properties; PrintingInfo printingInfo; @@ -613,21 +468,6 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QJson } text.append(tmp.getText()); - // Build the cardLang text by joining each face's translated text with - // the same separator as the English text. Any face missing a complete - // translation abandons the whole join, falling back to the English text. - if (localizedTextComplete) { - const QString partLocalizedText = tmp.getLocalizedText(); - if (partLocalizedText.isEmpty()) { - localizedTextComplete = false; - } else { - if (!localizedText.isEmpty()) { - localizedText.append(splitCardTextSeparator); - } - localizedText.append(partLocalizedText); - } - } - if (properties.isEmpty()) { properties = tmp.getProperties(); printingInfo = tmp.getPrintingInfo(); @@ -660,18 +500,6 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QJson } } CardInfoPtr newCard = addCard(name, text, isToken, std::move(properties), {}, printingInfo); - if (localizationEnabled && localizedTextComplete && !localizedText.isEmpty()) { - // Same priority policy as collectForeignData(): the joined text from the - // highest-priority set seen so far wins, applied once all printings are in. - LocalizedCardEntry entry; - entry.text = localizedText; - entry.priority = currentSet->getPriority(); - const QString entryKey = normalizeCardName(name); - const auto existing = splitLocalizedTexts.constFind(entryKey); - if (existing == splitLocalizedTexts.constEnd() || entry.priority < existing->priority) { - splitLocalizedTexts.insert(entryKey, entry); - } - } numCards++; } @@ -826,8 +654,6 @@ int OracleImporter::startImport() emit setIndexChanged(numCardsInSet, setIndex, curSetToParse.getLongName()); } - applyLocalizedData(); - emit setIndexChanged(0, setIndex, QString()); // total number of sets @@ -853,6 +679,4 @@ void OracleImporter::clear() cards.clear(); allSets.clear(); rawSetsData.clear(); - localizedEntries.clear(); - splitLocalizedTexts.clear(); } diff --git a/oracle/src/oracleimporter.h b/oracle/src/oracleimporter.h index c8d3a19bf..748056772 100644 --- a/oracle/src/oracleimporter.h +++ b/oracle/src/oracleimporter.h @@ -107,8 +107,7 @@ public: SplitCardPart(const QString &_name, const QString &_text, const QHash &_properties, - const PrintingInfo &_printingInfo, - const QString &_localizedText = QString()); + const PrintingInfo &_printingInfo); inline const QString &getName() const { return name; @@ -117,13 +116,6 @@ public: { return text; } - /** - * @brief The cardLang rules text of this face's foreignData entry, if any. - */ - inline const QString &getLocalizedText() const - { - return localizedText; - } inline const QHash &getProperties() const { return properties; @@ -136,18 +128,10 @@ public: private: QString name; QString text; - QString localizedText; QHash properties; PrintingInfo printingInfo; }; -struct LocalizedCardEntry -{ - QString name; - QString text; - CardSet::Priority priority = CardSet::PriorityLowest; -}; - class OracleImporter : public QObject { Q_OBJECT @@ -187,53 +171,12 @@ private: */ QAtomicInt importCancelled; - /** - * The ISO-639 language code whose foreignData is imported; "en" by default. - */ - QString cardLang = "en"; - - /** - * Whether cardLang is a supported language other than English, so per-card - * foreignData scanning can be skipped entirely when disabled. - */ - bool localizationEnabled = false; - - /** - * Localized name/text collected per imported card key while parsing sets, - * applied to the CardInfo objects by applyLocalizedData() once all - * printings have been seen so the best-priority one wins. - */ - QMap localizedEntries; - - /** - * cardLang rules text collected for split-card names while parsing sets, - * applied by applyLocalizedData(). Kept apart from localizedEntries because - * MTGJSON emits each split face as its own card object with the joined name - * on every foreignData entry: names and the per-face text join have different - * completeness and must not overwrite each other under the same key. - */ - QMap splitLocalizedTexts; - CardInfoPtr addCard(QString name, const QString &text, bool isToken, QHash properties, const QList &relatedCards, const PrintingInfo &printingInfo); - - /** - * Records the first foreignData entry matching cardLang for the given card - * key, keeping the entry from the highest-priority set seen so far. - * - * Multi-face cards (split, adventure, aftermath, prepare) pass collectText = - * false: MTGJSON emits one foreignData entry per face with the same joined - * name but only that face's text, so the name is collected here while the - * per-face texts are joined during the split-card merge. - */ - void collectForeignData(const QString &cardKey, - const CardSetPtr ¤tSet, - const QJsonObject &card, - bool collectText = true); signals: void setIndexChanged(int cardsImported, int setIndex, const QString &setName); void dataReadProgress(int bytesRead, int totalBytes); @@ -252,15 +195,6 @@ public: { progressReporting = enabled; } - /** - * Selects the ISO-639 language code whose foreignData is imported. - * English (the default) and unsupported codes disable localization. - */ - void setCardLang(const QString &lang); - const QString &getCardLang() const - { - return cardLang; - } /** * Scans the given JSON document for set metadata. Takes the data by value so * the wizard can hand over its decompressed buffer without copying it. @@ -278,12 +212,6 @@ public: { importCancelled.storeRelease(1); } - /** - * Applies the collected localized names/texts to the imported cards. - * Called automatically at the end of startImport(); exposed separately so - * tests can drive it after importing sets directly. - */ - void applyLocalizedData(); bool saveToFile(const QString &fileName, const QString &sourceUrl, const QString &sourceVersion); int importCardsFromSet(const CardSetPtr ¤tSet, const QJsonArray &cardsList); /** diff --git a/oracle/src/oraclewizard.cpp b/oracle/src/oraclewizard.cpp index 9ce0d7b51..dd3ac6459 100644 --- a/oracle/src/oraclewizard.cpp +++ b/oracle/src/oraclewizard.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include OracleWizard::OracleWizard(QWidget *parent) : QWizard(parent) @@ -38,9 +37,6 @@ OracleWizard::OracleWizard(QWidget *parent) : QWizard(parent) connect(&SettingsCache::instance().personal(), &PersonalSettings::langChanged, this, &OracleWizard::updateLanguage); importer = new OracleImporter(this); - // Import card text in the language the client displays, if supported: - // foreignData for any other language is never imported. - importer->setCardLang(SettingsCache::instance().cardsDisplay().getCardLang()); nam = new QNetworkAccessManager(this); diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp index af6118646..c4c7046c3 100644 --- a/servatrice/src/servatrice_database_interface.cpp +++ b/servatrice/src/servatrice_database_interface.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -100,59 +99,12 @@ bool Servatrice_DatabaseInterface::openDatabase() return false; } - if (sqlDatabase.driverName() != "QMYSQL") { - qCCritical(DatabaseInterfaceLog) - << poolStr - << "Error opening database: connection is not a MySQL/MariaDB database, Servatrice only " - "supports the QMYSQL driver (actual driver:" - << sqlDatabase.driverName() << ")."; - return false; - } - - bool strictModeCheckOk = false; - const bool strictModeEnabled = isStrictModeEnabled(strictModeCheckOk); - if (!strictModeCheckOk) { - qCCritical(DatabaseInterfaceLog) << poolStr - << "Error opening database: unable to determine whether MySQL/MariaDB strict " - "mode is enabled"; - return false; - } - if (strictModeEnabled) { - qCCritical(DatabaseInterfaceLog) << poolStr - << "Error opening database: MySQL/MariaDB strict mode is enabled, which " - "breaks most Servatrice database operations. Please disable strict mode " - "by removing STRICT_TRANS_TABLES and STRICT_ALL_TABLES from sql_mode, " - "for example by adding 'sql_mode=NO_ENGINE_SUBSTITUTION' under [mysqld] " - "in your my.cnf (or my.ini on Windows) and restarting the database " - "server."; - return false; - } - // reset all prepared statements qDeleteAll(preparedStatements); preparedStatements.clear(); return true; } -bool Servatrice_DatabaseInterface::isStrictModeEnabled(bool &ok) const -{ - ok = true; - - QSqlQuery query(sqlDatabase); - if (!query.exec("SELECT @@GLOBAL.sql_mode")) { - ok = false; - return false; - } - - const QStringList modes = query.next() ? query.value(0).toString().split(',') : QStringList(); - for (const QString &mode : modes) { - if (mode.trimmed() == "STRICT_TRANS_TABLES" || mode.trimmed() == "STRICT_ALL_TABLES") { - return true; - } - } - return false; -} - bool Servatrice_DatabaseInterface::checkSql() { if (!sqlDatabase.isValid()) { diff --git a/servatrice/src/servatrice_database_interface.h b/servatrice/src/servatrice_database_interface.h index f0e369449..a891c7a3d 100644 --- a/servatrice/src/servatrice_database_interface.h +++ b/servatrice/src/servatrice_database_interface.h @@ -32,7 +32,6 @@ private: bool checkUserIsIpBanned(const QString &ipAddress, QString &banReason, int &banSecondsRemaining); /** Must be called after checkSql and server is known to be in auth mode. */ bool checkUserIsNameBanned(QString const &userName, QString &banReason, int &banSecondsRemaining); - bool isStrictModeEnabled(bool &ok) const; protected: AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7bb834d7e..4f5dc88eb 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -12,12 +12,10 @@ add_test(NAME server_card_counter_test COMMAND server_card_counter_test) add_test(NAME server_counter_test COMMAND server_counter_test) add_test(NAME server_rate_limiter_test COMMAND server_rate_limiter_test) add_test(NAME server_developer_role_test COMMAND server_developer_role_test) -add_test(NAME server_game_join_test COMMAND server_game_join_test) add_test(NAME warning_categories_test COMMAND warning_categories_test) add_test(NAME lag_monitor_test COMMAND lag_monitor_test) add_test(NAME latency_tracker_test COMMAND latency_tracker_test) add_test(NAME metrics_registry_test COMMAND metrics_registry_test) -add_test(NAME loader_local_matching_test COMMAND loader_local_matching_test) add_test(NAME deck_hash_performance_test COMMAND deck_hash_performance_test) set_tests_properties(deck_hash_performance_test PROPERTIES TIMEOUT 15) @@ -35,23 +33,11 @@ add_executable(server_card_counter_test server_card_counter_test.cpp) add_executable(server_counter_test server_counter_test.cpp) add_executable(server_rate_limiter_test server_rate_limiter_test.cpp) add_executable(server_developer_role_test server_developer_role_test.cpp) -add_executable(server_game_join_test server_game_join_test.cpp) add_executable(warning_categories_test warning_categories_test.cpp) add_executable(lag_monitor_test ${CMAKE_SOURCE_DIR}/cockatrice/src/client/lag_monitor.cpp lag_monitor_test.cpp) target_include_directories(lag_monitor_test PRIVATE ${CMAKE_SOURCE_DIR}/cockatrice/src) add_executable(latency_tracker_test latency_tracker_test.cpp) add_executable(metrics_registry_test ../servatrice/src/metrics_registry.cpp metrics_registry_test.cpp) -add_executable( - loader_local_matching_test - ${CMAKE_SOURCE_DIR}/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp - ${CMAKE_SOURCE_DIR}/cockatrice/src/client/settings/cache_settings.cpp - ${CMAKE_SOURCE_DIR}/cockatrice/src/client/settings/card_counter_settings.cpp - ${CMAKE_SOURCE_DIR}/cockatrice/src/client/settings/shortcuts_settings.cpp - ${CMAKE_SOURCE_DIR}/cockatrice/src/client/network/update/client/release_channel.cpp - ${VERSION_STRING_CPP} - loader_local_matching_test.cpp -) -target_include_directories(loader_local_matching_test PRIVATE ${CMAKE_SOURCE_DIR}/cockatrice/src) find_package(GTest) @@ -89,12 +75,10 @@ if(NOT GTEST_FOUND) add_dependencies(server_counter_test gtest) add_dependencies(server_rate_limiter_test gtest) add_dependencies(server_developer_role_test gtest) - add_dependencies(server_game_join_test gtest) add_dependencies(warning_categories_test gtest) add_dependencies(lag_monitor_test gtest) add_dependencies(latency_tracker_test gtest) add_dependencies(metrics_registry_test gtest) - add_dependencies(loader_local_matching_test gtest) endif() include_directories(${GTEST_INCLUDE_DIRS}) @@ -130,10 +114,6 @@ target_link_libraries( server_developer_role_test libcockatrice_network libcockatrice_rng Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} ) -target_link_libraries( - server_game_join_test libcockatrice_network_server_remote libcockatrice_rng Threads::Threads ${GTEST_BOTH_LIBRARIES} - ${TEST_QT_MODULES} -) target_link_libraries( warning_categories_test libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} ) @@ -143,9 +123,6 @@ target_link_libraries( ) target_include_directories(metrics_registry_test PRIVATE ${CMAKE_SOURCE_DIR}/servatrice/src) target_link_libraries(metrics_registry_test ${TEST_QT_MODULES} Threads::Threads ${GTEST_BOTH_LIBRARIES}) -target_link_libraries( - loader_local_matching_test libcockatrice_settings Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} -) add_subdirectory(card_zone_algorithms) add_subdirectory(carddatabase) diff --git a/tests/carddatabase/carddatabase_test.cpp b/tests/carddatabase/carddatabase_test.cpp index 392f6395a..3fa0e3834 100644 --- a/tests/carddatabase/carddatabase_test.cpp +++ b/tests/carddatabase/carddatabase_test.cpp @@ -2,14 +2,9 @@ #include "test_card_database_path_provider.h" #include "gtest/gtest.h" -#include -#include -#include -#include -#include -#include #include #include + namespace { @@ -38,49 +33,6 @@ TEST(CardDatabaseTest, LoadXml) ASSERT_EQ(0, db->query()->getAllMainCardTypes().size()) << "Types not empty after clear"; ASSERT_EQ(NotLoaded, db->getLoadStatus()) << "Incorrect status after clear"; } - -TEST(CardDatabaseTest, Xml4LocalizedDataRoundTrip) -{ - NoopCardSetPriorityController controller; - CardSetPtr set = - CardSet::newInstance(&controller, "TST", "Test Set", "expansion", QDate(), CardSet::PriorityPrimary); - - QHash props; - props["manacost"] = "1R"; - PrintingInfo printing(set, LazyPropertiesHash(props)); - SetToPrintingsMap setsInfo; - setsInfo["TST"].append(printing); - - CardInfo::UiAttributes attributes = {.tableRow = 1}; - CardInfoPtr card = - CardInfo::newInstance("Lightning Bolt", "Deal 3 damage.", false, {}, {}, {}, setsInfo, attributes); - card->setLocalizedName("de", "Blitzschlag"); - card->setLocalizedText("de", "Blitzschlag fügt 3 Schadenspunkte zu."); - - SetNameMap sets; - sets.insert("TST", set); - CardNameMap cards; - cards.insert("Lightning Bolt", card); - - QTemporaryDir tempDir; - const QString fileName = tempDir.filePath("cards.xml"); - NoopCardPreferenceProvider prefProvider; - CockatriceXml4Parser writer(&prefProvider, &controller); - ASSERT_TRUE(writer.saveToFile({}, sets, cards, fileName)); - - CardDatabaseData data; - CockatriceXml4Parser parser(&prefProvider, &controller); - QFile file(fileName); - ASSERT_TRUE(file.open(QIODevice::ReadOnly)); - parser.parseFileInto(file, data); - - CardInfoPtr loaded = data.cards.value("Lightning Bolt"); - ASSERT_FALSE(loaded.isNull()); - ASSERT_EQ(loaded->getName(), "Lightning Bolt"); - ASSERT_EQ(loaded->getLocalizedName("de"), "Blitzschlag"); - ASSERT_EQ(loaded->getLocalizedText("de"), "Blitzschlag fügt 3 Schadenspunkte zu."); - ASSERT_EQ(loaded->getLocalizedText("fr"), "Deal 3 damage."); -} } // namespace int main(int argc, char **argv) diff --git a/tests/loader_local_matching_test.cpp b/tests/loader_local_matching_test.cpp deleted file mode 100644 index 7ffa7157d..000000000 --- a/tests/loader_local_matching_test.cpp +++ /dev/null @@ -1,187 +0,0 @@ -#include "client/settings/cache_settings.h" -#include "interface/card_picture_loader/card_picture_loader_local.h" - -#include "gtest/gtest.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace -{ - -/** - * @brief Builds an ExactCard with the requested identity fields. - * - * Mirrors how the client constructs cards: the set short name feeds tryLoad()'s - * setName, and the "num" printing property feeds the collector number. - */ -ExactCard cardFor(const QString &name, const QString &setShortName, const QString &collectorNumber) -{ - CardSetPtr set; - if (!setShortName.isEmpty()) { - set = CardSet::newInstance(new NoopCardSetPriorityController(), setShortName, setShortName); - } - - LazyPropertiesHash properties; - if (!collectorNumber.isEmpty()) { - properties.insert("num", collectorNumber); - } - - return ExactCard(CardInfo::newInstance(name), PrintingInfo(set, properties)); -} - -class LocalMatcherTest : public ::testing::Test -{ -protected: - QTemporaryDir tempDir; ///< Sandboxed "pics" root for every test. - CardPictureLoaderLocal *loader = nullptr; ///< Constructed per test against the sandboxed paths. - - QString picsPath() const - { - return tempDir.path() + "/pics"; - } - - void SetUp() override - { - // The loader ctor snapshots the global picture paths once, so point them at the - // sandbox before constructing it. - SettingsCache::instance().paths().setPicsPath(picsPath()); - SettingsCache::instance().paths().setCustomPicsPath(picsPath() + "/CUSTOM/"); - - loader = new CardPictureLoaderLocal(nullptr); - } - - void TearDown() override - { - delete loader; - loader = nullptr; - } - - /** - * @brief Writes a valid 1x1 PNG under the sandboxed pics path. - */ - void writePngUnderPics(const QString &relativePath, const QColor &color = Qt::red) - { - const QString fullPath = picsPath() + "/" + relativePath; - ASSERT_TRUE(QDir().mkpath(QFileInfo(fullPath).absolutePath())); - - QImage image(1, 1, QImage::Format_RGB32); - image.fill(color); - - QImageWriter writer(fullPath, "PNG"); - ASSERT_TRUE(writer.write(image)); - } -}; - -TEST_F(LocalMatcherTest, ExactMatchBareFileWinsOverSuffixedCompanion) -{ - writePngUnderPics("downloadedPics/TestCard.png"); - writePngUnderPics("downloadedPics/TestCard (1).png"); - - const QImage image = loader->tryLoad(cardFor("TestCard", "", "")); - - EXPECT_FALSE(image.isNull()) << "The bare TestCard.png must be picked over its suffixed companion"; -} - -TEST_F(LocalMatcherTest, SuffixedFileWithoutExactMatchIsNotLoaded) -{ - // The pre-refactor prefix match would have accepted "TestCard (1).png" for "TestCard". - writePngUnderPics("downloadedPics/TestCard (1).png"); - - const QImage image = loader->tryLoad(cardFor("TestCard", "", "")); - - EXPECT_TRUE(image.isNull()) << "A suffixed file must not satisfy an exact card-name lookup"; -} - -TEST_F(LocalMatcherTest, SetFolderLookupIgnoresSuffixedFiles) -{ - writePngUnderPics("M10/TestCard (1).png"); - - const QImage image = loader->tryLoad(cardFor("TestCard", "M10", "")); - - EXPECT_TRUE(image.isNull()) << "Set-folder lookups must also require an exact name match"; -} - -TEST_F(LocalMatcherTest, SetFolderLookupStillResolvesExactFile) -{ - writePngUnderPics("M10/TestCard.png"); - writePngUnderPics("M10/TestCard (1).png"); - - const QImage image = loader->tryLoad(cardFor("TestCard", "M10", "")); - - EXPECT_FALSE(image.isNull()) << "The exact file in the set folder must still resolve"; -} - -TEST_F(LocalMatcherTest, RootDownloadedPicsFallbackResolvesSchemeFilename) -{ - // Non-set-folder export schemes (Name_Set_Collector) write straight into downloadedPics/. - writePngUnderPics("downloadedPics/TestCard_M10_1.png"); - - const QImage image = loader->tryLoad(cardFor("TestCard", "M10", "1")); - - EXPECT_FALSE(image.isNull()) << "downloadedPics/TestCard_M10_1.png must resolve via the root fallback"; -} - -TEST_F(LocalMatcherTest, RootFallbackResolvesDashSeparatedVariant) -{ - writePngUnderPics("downloadedPics/TestCard-M10-1.png"); - - const QImage image = loader->tryLoad(cardFor("TestCard", "M10", "1")); - - EXPECT_FALSE(image.isNull()) << "The dash-separated import variant must resolve via the root fallback"; -} - -TEST_F(LocalMatcherTest, DownloadedPicsSetSubfolderStillResolves) -{ - writePngUnderPics("downloadedPics/M10/TestCard_M10_1.png"); - - const QImage image = loader->tryLoad(cardFor("TestCard", "M10", "1")); - - EXPECT_FALSE(image.isNull()) << "The set-subfolder export scheme must keep resolving"; -} - -TEST_F(LocalMatcherTest, SetFolderCandidateTakesPrecedenceOverRootFallback) -{ - writePngUnderPics("M10/TestCard_M10_1.png", Qt::red); - writePngUnderPics("downloadedPics/TestCard_M10_1.png", Qt::blue); - - const QImage image = loader->tryLoad(cardFor("TestCard", "M10", "1")); - - ASSERT_FALSE(image.isNull()); - EXPECT_EQ(image.pixelColor(0, 0), QColor(Qt::red)) << "The set-folder candidate must be preferred"; -} - -} // namespace - -int main(int argc, char **argv) -{ - // Redirect SettingsCache reads/writes (app-data location) away from the real user profile. - QStandardPaths::setTestModeEnabled(true); - - // Some CI containers run as a uid without a passwd entry (e.g. GitHub's docker - // runner), so HOME resolves to "/" and the test-mode qttest data dir cannot be - // created. SettingsCache's QSettings then silently drops every write, reads come - // back empty, and the paths the loader searches are "". Give the test a writable - // HOME for the duration of the run so settings behave like on a normal machine. - QTemporaryDir home; - if (home.isValid()) { - qputenv("HOME", home.path().toLocal8Bit()); - } - - QCoreApplication app(argc, argv); - QLoggingCategory::setFilterRules("card_picture_loader.*=false\nsettings_cache.*=false"); - - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} \ No newline at end of file diff --git a/tests/oracle/oracle_importer_test.cpp b/tests/oracle/oracle_importer_test.cpp index f66616e37..66c7bdbe1 100644 --- a/tests/oracle/oracle_importer_test.cpp +++ b/tests/oracle/oracle_importer_test.cpp @@ -72,18 +72,6 @@ protected: return card; } - // Helper: build a single MTGJSON foreignData entry - QJsonObject makeForeignEntry(const QString &language, const QString &name, const QString &text) - { - QJsonObject entry; - entry["language"] = language; - entry["name"] = name; - if (!text.isEmpty()) { - entry["text"] = text; - } - return entry; - } - NoopCardSetPriorityController *controller; OracleImporter *importer; CardSetPtr set; @@ -884,243 +872,6 @@ TEST_F(OracleImporterTest, DisablingProgressReportingSuppressesScanEmissions) ASSERT_GT(emissions, 0); } -// Localized card text tests -// ============================================================================ - -TEST_F(OracleImporterTest, ImportsLocalizedTextForRequestedLanguage) -{ - QJsonObject card = makeCard("Lightning Bolt"); - card["foreignData"] = - QJsonArray{makeForeignEntry("German", "Blitzschlag", "Blitzschlag fügt 3 Schadenspunkte zu.")}; - QJsonArray cards{card}; - - importer->setCardLang("de"); - importer->importCardsFromSet(set, cards); - importer->applyLocalizedData(); - - auto result = importer->getCardList().value("Lightning Bolt"); - ASSERT_FALSE(result.isNull()); - ASSERT_EQ(result->getLocalizedName("de"), "Blitzschlag"); - ASSERT_EQ(result->getLocalizedText("de"), "Blitzschlag fügt 3 Schadenspunkte zu."); - // English identity untouched - ASSERT_EQ(result->getName(), "Lightning Bolt"); - ASSERT_EQ(result->getText(), "Rules text."); -} - -TEST_F(OracleImporterTest, ImportsLocalizedNameAndTextForMultiFaceCards) -{ - // MTGJSON reports multi-face cards (adventure/split/aftermath/prepare) as one - // card object per face; every face carries the joined name but only its own - // face's rules text in foreignData. The importer joins the per-face texts with - // the same separator as the English merge. - QJsonObject front = makeCard("Disruptive Stormbrood // Petty Revenge"); - front["layout"] = "adventure"; - front["faceName"] = "Disruptive Stormbrood"; - front["side"] = "a"; - front["foreignData"] = QJsonArray{ - makeForeignEntry("German", "Disruptive Stormbrood // Kleinliche Rache", - "Fliegend\nWenn diese Kreatur ins Spiel kommt, zerstöre bis zu ein Artefakt oder eine " - "Verzauberung deiner Wahl.")}; - QJsonObject back = makeCard("Disruptive Stormbrood // Petty Revenge"); - back["layout"] = "adventure"; - back["faceName"] = "Petty Revenge"; - back["side"] = "b"; - back["text"] = "Destroy target creature."; - back["foreignData"] = QJsonArray{makeForeignEntry("German", "Disruptive Stormbrood // Kleinliche Rache", - "Zerstöre eine Kreatur deiner Wahl mit Stärke 3 oder weniger.")}; - QJsonArray cards{front, back}; - - importer->setCardLang("de"); - importer->importCardsFromSet(set, cards); - importer->applyLocalizedData(); - - auto result = importer->getCardList().value("Disruptive Stormbrood // Petty Revenge"); - ASSERT_FALSE(result.isNull()); - ASSERT_EQ(result->getLocalizedName("de"), "Disruptive Stormbrood // Kleinliche Rache"); - ASSERT_EQ(result->getLocalizedText("de"), - "Fliegend\nWenn diese Kreatur ins Spiel kommt, zerstöre bis zu ein Artefakt oder eine Verzauberung " - "deiner Wahl.\n\n---\n\nZerstöre eine Kreatur deiner Wahl mit Stärke 3 oder weniger."); - // English identity untouched - ASSERT_EQ(result->getName(), "Disruptive Stormbrood // Petty Revenge"); - ASSERT_EQ(result->getText(), "Rules text.\n\n---\n\nDestroy target creature."); -} - -TEST_F(OracleImporterTest, MultiFaceCardsWithoutCompleteForeignTextKeepEnglishText) -{ - // Both faces must carry a foreignData text for the joined text; otherwise the - // rules text stays English while the localized name (from a later complete - // printing) is still applied. - QJsonObject front = makeCard("Wear // Tear"); - front["layout"] = "split"; - front["faceName"] = "Wear"; - front["side"] = "a"; - front["foreignData"] = QJsonArray{makeForeignEntry("German", "Verschleiß // Zerrreißung", "Verschleiß-Text.")}; - QJsonObject back = makeCard("Wear // Tear"); - back["layout"] = "split"; - back["faceName"] = "Tear"; - back["side"] = "b"; - back["text"] = "Tear rules text."; - back["foreignData"] = QJsonArray{makeForeignEntry("German", "Verschleiß // Zerrreißung", "")}; - QJsonArray cards{front, back}; - - importer->setCardLang("de"); - importer->importCardsFromSet(set, cards); - importer->applyLocalizedData(); - - auto result = importer->getCardList().value("Wear // Tear"); - ASSERT_FALSE(result.isNull()); - // The joined name is still applied. - ASSERT_EQ(result->getLocalizedName("de"), "Verschleiß // Zerrreißung"); - // The incomplete text must not become the card's localized text. - ASSERT_TRUE(result->getLocalizedTexts().isEmpty()); - ASSERT_EQ(result->getText(), "Rules text.\n\n---\n\nTear rules text."); -} - -TEST_F(OracleImporterTest, DefaultLanguageSkipsForeignData) -{ - QJsonObject card = makeCard("Lightning Bolt"); - card["foreignData"] = - QJsonArray{makeForeignEntry("German", "Blitzschlag", "Blitzschlag fügt 3 Schadenspunkte zu.")}; - QJsonArray cards{card}; - - // cardLang defaults to "en" — foreignData must never be imported - importer->importCardsFromSet(set, cards); - importer->applyLocalizedData(); - - auto result = importer->getCardList().value("Lightning Bolt"); - ASSERT_FALSE(result.isNull()); - ASSERT_TRUE(result->getLocalizedNames().isEmpty()); - ASSERT_TRUE(result->getLocalizedTexts().isEmpty()); -} - -TEST_F(OracleImporterTest, UnsupportedLanguageSkipsForeignData) -{ - QJsonObject card = makeCard("Lightning Bolt"); - card["foreignData"] = QJsonArray{makeForeignEntry("xx", "Kochanie", "Grzmot uderza.")}; - QJsonArray cards{card}; - - importer->setCardLang("xx"); - importer->importCardsFromSet(set, cards); - importer->applyLocalizedData(); - - auto result = importer->getCardList().value("Lightning Bolt"); - ASSERT_FALSE(result.isNull()); - ASSERT_TRUE(result->getLocalizedNames().isEmpty()); -} - -TEST_F(OracleImporterTest, NonMatchingLanguageNotCollected) -{ - QJsonObject card = makeCard("Lightning Bolt"); - card["foreignData"] = QJsonArray{makeForeignEntry("French", "Éclair", "L'Éclair inflige 3 blessures.")}; - QJsonArray cards{card}; - - importer->setCardLang("de"); - importer->importCardsFromSet(set, cards); - importer->applyLocalizedData(); - - auto result = importer->getCardList().value("Lightning Bolt"); - ASSERT_FALSE(result.isNull()); - ASSERT_TRUE(result->getLocalizedNames().isEmpty()); -} - -TEST_F(OracleImporterTest, HigherPrioritySetWinsForReprint) -{ - // First printing in a reprint set, then another in a (more authoritative) - // core set: the core set's German text must win even though it was seen later. - QJsonObject reprintCard = makeCard("Lightning Bolt"); - reprintCard["foreignData"] = QJsonArray{makeForeignEntry("German", "Blitzschlag", "Älterer deutscher Text.")}; - CardSetPtr reprintSet = - CardSet::newInstance(controller, "TS2", "Second Set", QString(), QDate(), CardSet::PriorityReprint); - importer->setCardLang("de"); - importer->importCardsFromSet(reprintSet, QJsonArray{reprintCard}); - - QJsonObject primaryCard = makeCard("Lightning Bolt"); - primaryCard["foreignData"] = - QJsonArray{makeForeignEntry("German", "Blitzschlag", "Blitzschlag fügt 3 Schadenspunkte zu.")}; - CardSetPtr primarySet = - CardSet::newInstance(controller, "TS3", "Third Set", QString(), QDate(), CardSet::PriorityPrimary); - importer->importCardsFromSet(primarySet, QJsonArray{primaryCard}); - importer->applyLocalizedData(); - - auto result = importer->getCardList().value("Lightning Bolt"); - ASSERT_FALSE(result.isNull()); - ASSERT_EQ(result->getLocalizedName("de"), "Blitzschlag"); - ASSERT_EQ(result->getLocalizedText("de"), "Blitzschlag fügt 3 Schadenspunkte zu."); - ASSERT_EQ(importer->getCardList().size(), 1); -} - -TEST_F(OracleImporterTest, HigherPrioritySplitSetWinsForReprint) -{ - // Split cards print each face as its own card object; the joined text is - // collected per set with the same priority policy as single-face cards, so a - // reprint set's German text must yield to the core set's even when reprints - // are imported first. - QJsonObject reprintFront = makeCard("Wear // Tear"); - reprintFront["layout"] = "split"; - reprintFront["faceName"] = "Wear"; - reprintFront["side"] = "a"; - reprintFront["foreignData"] = - QJsonArray{makeForeignEntry("German", "Verschleiß // Zerrreißung", "Wear alter Text.")}; - QJsonObject reprintBack = makeCard("Wear // Tear"); - reprintBack["layout"] = "split"; - reprintBack["faceName"] = "Tear"; - reprintBack["side"] = "b"; - reprintBack["foreignData"] = - QJsonArray{makeForeignEntry("German", "Verschleiß // Zerrreißung", "Tear alter Text.")}; - CardSetPtr reprintSet = - CardSet::newInstance(controller, "TS2", "Second Set", QString(), QDate(), CardSet::PriorityReprint); - importer->setCardLang("de"); - importer->importCardsFromSet(reprintSet, QJsonArray{reprintFront, reprintBack}); - - QJsonObject primaryFront = makeCard("Wear // Tear"); - primaryFront["layout"] = "split"; - primaryFront["faceName"] = "Wear"; - primaryFront["side"] = "a"; - primaryFront["foreignData"] = - QJsonArray{makeForeignEntry("German", "Verschleiß // Zerrreißung", "Wear neuer Text.")}; - QJsonObject primaryBack = makeCard("Wear // Tear"); - primaryBack["layout"] = "split"; - primaryBack["faceName"] = "Tear"; - primaryBack["side"] = "b"; - primaryBack["foreignData"] = - QJsonArray{makeForeignEntry("German", "Verschleiß // Zerrreißung", "Tear neuer Text.")}; - CardSetPtr primarySet = - CardSet::newInstance(controller, "TS3", "Third Set", QString(), QDate(), CardSet::PriorityPrimary); - importer->importCardsFromSet(primarySet, QJsonArray{primaryFront, primaryBack}); - importer->applyLocalizedData(); - - auto result = importer->getCardList().value("Wear // Tear"); - ASSERT_FALSE(result.isNull()); - ASSERT_EQ(result->getLocalizedName("de"), "Verschleiß // Zerrreißung"); - ASSERT_EQ(result->getLocalizedText("de"), "Wear neuer Text.\n\n---\n\nTear neuer Text."); - ASSERT_EQ(importer->getCardList().size(), 1); -} - -TEST_F(OracleImporterTest, StartImportAppliesLocalizedData) -{ - QJsonObject card = makeCard("Lightning Bolt"); - card["foreignData"] = - QJsonArray{makeForeignEntry("Portuguese (Brazil)", "Raio", "Raio causa 3 de dano a qualquer alvo.")}; - QJsonObject dataSet; - dataSet["code"] = "tst"; - dataSet["name"] = "Test Set"; - dataSet["type"] = "expansion"; - dataSet["releaseDate"] = "2024-01-01"; - dataSet["cards"] = QJsonArray{card}; - - QJsonObject root; - root["data"] = QJsonObject{{"TST", dataSet}}; - - importer->setCardLang("pt"); - ASSERT_TRUE(importer->readSetsFromByteArray(QJsonDocument(root).toJson(QJsonDocument::Compact))); - ASSERT_EQ(importer->startImport(), 1); - - auto result = importer->getCardList().value("Lightning Bolt"); - ASSERT_FALSE(result.isNull()); - ASSERT_EQ(result->getLocalizedName("pt"), "Raio"); - ASSERT_EQ(result->getLocalizedText("pt"), "Raio causa 3 de dano a qualquer alvo."); -} - int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); diff --git a/tests/server_game_join_test.cpp b/tests/server_game_join_test.cpp deleted file mode 100644 index c84e4e066..000000000 --- a/tests/server_game_join_test.cpp +++ /dev/null @@ -1,174 +0,0 @@ -/** @file server_game_join_test.cpp - * @brief Tests for the moderator/judge game-entry restriction override in Server_Game::checkJoin. - * @ingroup Tests - */ - -#include "game/server_game.h" -#include "server.h" -#include "server_database_interface.h" -#include "server_room.h" - -#include -#include -#include - -RNG_Abstract *rng = nullptr; // referenced by the server_remote library - -namespace -{ - -class MockDatabaseInterface : public Server_DatabaseInterface -{ -public: - AuthenticationResult checkUserPassword(Server_ProtocolHandler *, - const QString &, - const QString &, - const QString &, - QString &, - int &, - bool) override - { - return NotLoggedIn; - } - int getNextReplayId() override - { - return 1; - } - int getNextGameId() override - { - return 1; - } - int getActiveUserCount(QString) override - { - return 0; - } - ServerInfo_User getUserData(const QString &, bool) override - { - return ServerInfo_User(); - } -}; - -class FakeServer : public Server -{ -public: - FakeServer() - { - setDatabaseInterface(new MockDatabaseInterface()); - } -}; - -class GameJoinOverrideTest : public ::testing::Test -{ -protected: - FakeServer server; - Server_Room room{0, 0, "", "", "", "", false, "", {}, &server}; - ServerInfo_User creator; - ServerInfo_User plainUser; - ServerInfo_User unregisteredJudge; - ServerInfo_User moderator; - ServerInfo_User judge; - Server_Game *game = nullptr; - - void SetUp() override - { - creator.set_name("creator"); - creator.set_user_level(ServerInfo_User::IsUser | ServerInfo_User::IsRegistered); - plainUser.set_name("plain-user"); - plainUser.set_user_level(ServerInfo_User::IsUser | ServerInfo_User::IsRegistered); - unregisteredJudge.set_name("unregistered-judge"); - unregisteredJudge.set_user_level(ServerInfo_User::IsUser | ServerInfo_User::IsJudge); - moderator.set_name("moderator"); - moderator.set_user_level(ServerInfo_User::IsUser | ServerInfo_User::IsRegistered | - ServerInfo_User::IsModerator); - judge.set_name("judge"); - judge.set_user_level(ServerInfo_User::IsUser | ServerInfo_User::IsRegistered | ServerInfo_User::IsJudge); - } - - void TearDown() override - { - delete game; - } - - Server_Game *makeGame(bool passwordProtected, bool onlyRegistered, bool onlyBuddies, bool spectatorsAllowed) - { - GameConfig config{.creatorInfo = creator, - .gameId = 1, - .description = QString(), - .password = passwordProtected ? "secret" : QString(), - .maxPlayers = 2, - .gameTypes = QList(), - .onlyBuddies = onlyBuddies, - .onlyRegistered = onlyRegistered, - .spectatorsAllowed = spectatorsAllowed, - .spectatorsNeedPassword = true, - .spectatorsCanTalk = false, - .spectatorsSeeEverything = false, - .startingLifeTotal = 20, - .shareDecklistsOnLoad = false}; - return new Server_Game(config, &room); - } -}; - -TEST_F(GameJoinOverrideTest, StaffBypassPasswordRestriction) -{ - game = makeGame(true, false, false, true); - - // A plain user cannot override the password even with the override flag set. - EXPECT_EQ(game->checkJoin(&plainUser, "wrong", false, true, false), Response::RespWrongPassword); - // Moderators and judges may enter any game regardless of the password. - EXPECT_EQ(game->checkJoin(&moderator, "wrong", false, true, false), Response::RespOk); - EXPECT_EQ(game->checkJoin(&judge, "wrong", false, true, false), Response::RespOk); - // Without the override flag judges are still subject to the password. - EXPECT_EQ(game->checkJoin(&judge, "wrong", false, false, true), Response::RespWrongPassword); - EXPECT_EQ(game->checkJoin(&judge, "secret", false, false, true), Response::RespOk); -} - -TEST_F(GameJoinOverrideTest, StaffBypassRegisteredOnlyRestriction) -{ - game = makeGame(false, true, false, true); - - // Without the override flag the only-registered restriction still applies. - EXPECT_EQ(game->checkJoin(&unregisteredJudge, QString(), false, false, false), Response::RespUserLevelTooLow); - // An unregistered judge may enter when overriding restrictions. - EXPECT_EQ(game->checkJoin(&unregisteredJudge, QString(), false, true, false), Response::RespOk); -} - -TEST_F(GameJoinOverrideTest, StaffBypassBuddiesOnlyRestriction) -{ - game = makeGame(false, false, true, true); - - // A plain user who is not on the creator's buddy list gets rejected. - EXPECT_EQ(game->checkJoin(&plainUser, QString(), false, true, false), Response::RespOnlyBuddies); - // Moderators and judges bypass the buddies-only restriction. - EXPECT_EQ(game->checkJoin(&moderator, QString(), false, true, false), Response::RespOk); - EXPECT_EQ(game->checkJoin(&judge, QString(), false, true, false), Response::RespOk); -} - -TEST_F(GameJoinOverrideTest, StaffBypassSpectatorsNotAllowedRestriction) -{ - game = makeGame(false, false, false, false); - - // A plain user cannot spectate when the game disallows spectators. - EXPECT_EQ(game->checkJoin(&plainUser, QString(), true, false, false), Response::RespSpectatorsNotAllowed); - // Moderators and judges may spectate any game regardless of the password - // and the spectator restriction. - EXPECT_EQ(game->checkJoin(&moderator, "wrong", true, true, false), Response::RespOk); - EXPECT_EQ(game->checkJoin(&judge, "wrong", true, true, false), Response::RespOk); -} - -TEST_F(GameJoinOverrideTest, JudgeOverrideDoesNotGrantJudgeJoinToPlainUser) -{ - game = makeGame(false, false, false, true); - - // joining with join_as_judge still requires the judge flag even when overriding. - EXPECT_EQ(game->checkJoin(&plainUser, QString(), false, true, true), Response::RespUserLevelTooLow); - EXPECT_EQ(game->checkJoin(&judge, QString(), false, true, true), Response::RespOk); -} - -} // namespace - -int main(int argc, char **argv) -{ - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} \ No newline at end of file diff --git a/tests/settings/settings_defaults_test.cpp b/tests/settings/settings_defaults_test.cpp index c8c5ad4c8..eda778ae9 100644 --- a/tests/settings/settings_defaults_test.cpp +++ b/tests/settings/settings_defaults_test.cpp @@ -386,19 +386,6 @@ TEST_F(SettingsDefaultsTest, Appearance_HomeTabDisplayCardName_Default) ASSERT_EQ(s.getHomeTabDisplayCardName(), true); } -TEST_F(SettingsDefaultsTest, Appearance_HomeTabBackgroundDim_Default) -{ - AppearanceSettings s(settingsPath, nullptr); - ASSERT_EQ(s.getHomeTabBackgroundDim(), true); -} - -TEST_F(SettingsDefaultsTest, Appearance_HomeTabBackgroundDim_SetAndGet) -{ - AppearanceSettings s(settingsPath, nullptr); - s.setHomeTabBackgroundDim(false); - ASSERT_EQ(s.getHomeTabBackgroundDim(), false); -} - // --- InterfaceSettings --- TEST_F(SettingsDefaultsTest, Interface_ShowStatusBar_Default) @@ -562,19 +549,6 @@ TEST_F(SettingsDefaultsTest, CardsDisplay_ArrowDrawAnimation_Default) ASSERT_EQ(s.getArrowDrawAnimation(), true); } -TEST_F(SettingsDefaultsTest, CardsDisplay_CardLang_Default) -{ - CardsDisplaySettings s(settingsPath, nullptr); - ASSERT_EQ(s.getCardLang(), QString("en")); -} - -TEST_F(SettingsDefaultsTest, CardsDisplay_CardLang_SetAndGet) -{ - CardsDisplaySettings s(settingsPath, nullptr); - s.setCardLang("de"); - ASSERT_EQ(s.getCardLang(), QString("de")); -} - // --- VisualDeckStorageSettings --- TEST_F(SettingsDefaultsTest, VisualDeckStorage_SortingOrder_Default)