diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 6ca634389..b631f32d3 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -152,7 +152,7 @@ jobs: env: CACHE: ${{ github.workspace }}/.cache/${{ matrix.distro }}${{ matrix.version }} # directory for caching docker image and ccache CCACHE_EVICTION_AGE: 7d - CCACHE_SIZE: 600M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy + CCACHE_SIZE: 550M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy CMAKE_GENERATOR: 'Ninja' NAME: ${{ matrix.distro }}${{ matrix.version }} @@ -342,7 +342,7 @@ jobs: timeout-minutes: 100 env: CCACHE_DIR: ${{ github.workspace }}/.cache/ - CCACHE_SIZE: 600M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy + CCACHE_SIZE: 550M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy steps: - name: "Checkout" diff --git a/README.md b/README.md index 5935bb540..f22df461f 100644 --- a/README.md +++ b/README.md @@ -149,15 +149,15 @@ You can then
The following flags (with their non-default values) can be passed to `cmake`: -| Flag | Description | -| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | -| `-DWITH_SERVER=1` | Build Servatrice server | -| `-DWITH_CLIENT=0` | Don't build Cockatrice client | -| `-DWITH_ORACLE=0` | Don't build Oracle card database tool | -| `-DCMAKE_BUILD_TYPE=Debug` | Compile in debug mode
Enables extra logging output, debug symbols, and much more verbose compiler warnings | -| `-DWARNING_AS_ERROR=0` | Don't treat compilation warnings as errors in debug mode | -| `-DUPDATE_TRANSLATIONS=1` | Configure `make` to update the translation .ts files for new strings in the source code
**Note:** `make clean` will remove the .ts files | -| `-DTEST=1` | Enable regression tests
**Note:** `make test` to run tests, *googletest* will be downloaded if not available | +| Flag | Description | +| --- | --- | +| `-DWITH_SERVER=1` | Build Servatrice server | +| `-DWITH_CLIENT=0` | Don't build Cockatrice client | +| `-DWITH_ORACLE=0` | Don't build Oracle card database tool | +| `-DCMAKE_BUILD_TYPE=Debug` | Compile in debug mode
Enables extra logging output, debug symbols, and much more verbose compiler warnings | +| `-DWARNING_AS_ERROR=0` | Don't treat compilation warnings as errors in debug mode | +| `-DUPDATE_TRANSLATIONS=1` | Configure `make` to update the translation .ts files for new strings in the source code
**Note:** `make clean` will remove the .ts files | +| `-DTEST=1` | Enable regression tests
**Note:** `make test` to run tests, *googletest* will be downloaded if not available | # Run diff --git a/cmake/FindQtRuntime.cmake b/cmake/FindQtRuntime.cmake index 971c9094d..0259d12e1 100644 --- a/cmake/FindQtRuntime.cmake +++ b/cmake/FindQtRuntime.cmake @@ -28,7 +28,7 @@ if(WITH_CLIENT) ) endif() if(WITH_ORACLE) - set(_ORACLE_NEEDED Concurrent Network Svg Widgets Xml) + set(_ORACLE_NEEDED Concurrent Network Svg Widgets) endif() if(TEST) # Union of Qt modules required across all test targets (independent of application targets). diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 4263fc6e2..b6050a1bd 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -166,7 +166,6 @@ set(cockatrice_SOURCES src/interface/widgets/cards/additional_info/mana_cost_widget.cpp src/interface/widgets/cards/additional_info/mana_symbol_widget.cpp src/interface/widgets/cards/art_crop_attribution.cpp - src/interface/widgets/cards/card_art_utils.cpp src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp src/interface/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.cpp @@ -382,7 +381,6 @@ set(cockatrice_SOURCES src/interface/widgets/tabs/tab_card_art_rules.cpp src/interface/widgets/tabs/tab_deck_editor.cpp src/interface/widgets/tabs/tab_deck_storage.cpp - src/interface/widgets/tabs/tab_developer.cpp src/interface/widgets/tabs/tab_game.cpp src/interface/widgets/tabs/tab_home.cpp src/interface/widgets/tabs/tab_logs.cpp @@ -403,7 +401,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 +528,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/client/network/connection_controller/remote_connection_controller.cpp b/cockatrice/src/client/network/connection_controller/remote_connection_controller.cpp index c1598bd25..890a621c8 100644 --- a/cockatrice/src/client/network/connection_controller/remote_connection_controller.cpp +++ b/cockatrice/src/client/network/connection_controller/remote_connection_controller.cpp @@ -1,6 +1,5 @@ #include "remote_connection_controller.h" -#include "../../../interface/pixel_map_generator.h" #include "../../settings/cache_settings.h" #include "../interface/widgets/dialogs/dlg_connect.h" #include "../interface/widgets/dialogs/dlg_forgot_password_challenge.h" @@ -181,7 +180,7 @@ void ConnectionController::onServerShutdownEvent(const Event_ServerShutdown &eve "games will be lost.\nReason for shutdown: %1", "", event.minutes()) .arg(QString::fromStdString(event.reason()))); - serverShutdownMessageBox.setIconPixmap(themePixmap(QStringLiteral("cockatrice")).scaled(64, 64)); + serverShutdownMessageBox.setIconPixmap(QPixmap("theme:cockatrice").scaled(64, 64)); serverShutdownMessageBox.setText(tr("Scheduled server shutdown")); serverShutdownMessageBox.setWindowModality(Qt::ApplicationModal); serverShutdownMessageBox.setVisible(true); diff --git a/cockatrice/src/client/sound_engine.cpp b/cockatrice/src/client/sound_engine.cpp index 96cafa3d3..18de2264d 100644 --- a/cockatrice/src/client/sound_engine.cpp +++ b/cockatrice/src/client/sound_engine.cpp @@ -94,7 +94,7 @@ QStringMap &SoundEngine::getAvailableThemes() QDir dir; availableThemes.clear(); - // Load themes from user profile dir + // load themes from user profile dir dir.setPath(SettingsCache::instance().getDataPath() + "/sounds"); @@ -104,7 +104,7 @@ QStringMap &SoundEngine::getAvailableThemes() } } - // Load themes from Cockatrice system dir + // load themes from cockatrice system dir dir.setPath(qApp->applicationDirPath() + #ifdef Q_OS_MAC "/../Resources/sounds" diff --git a/cockatrice/src/filters/filter_builder.cpp b/cockatrice/src/filters/filter_builder.cpp index f109fbcd3..785f753e7 100644 --- a/cockatrice/src/filters/filter_builder.cpp +++ b/cockatrice/src/filters/filter_builder.cpp @@ -1,6 +1,5 @@ #include "filter_builder.h" -#include "../interface/pixel_map_generator.h" #include "../interface/widgets/utility/custom_line_edit.h" #include @@ -22,7 +21,7 @@ FilterBuilder::FilterBuilder(QWidget *parent) : QWidget(parent) typeCombo->addItem(CardFilter::typeName(static_cast(i)), QVariant(i)); } - QPushButton *ok = new QPushButton(themePixmap(QStringLiteral("icons/increment")), QString()); + QPushButton *ok = new QPushButton(QPixmap("theme:icons/increment"), QString()); ok->setObjectName("ok"); ok->setMaximumSize(20, 20); diff --git a/cockatrice/src/game/board/counter_state.cpp b/cockatrice/src/game/board/counter_state.cpp index 0970e4272..6da18b662 100644 --- a/cockatrice/src/game/board/counter_state.cpp +++ b/cockatrice/src/game/board/counter_state.cpp @@ -13,12 +13,12 @@ CounterState *CounterState::fromProto(const ServerInfo_Counter &counter, QObject convertColorToQColor(counter.counter_color()), counter.radius(), counter.count(), parent); } -void CounterState::setValue(int newValue, bool skipDamageAnimation) +void CounterState::setValue(int newValue) { if (newValue == value) { return; } int old = value; value = newValue; - emit valueChanged(old, newValue, skipDamageAnimation); + emit valueChanged(old, newValue); } \ No newline at end of file diff --git a/cockatrice/src/game/board/counter_state.h b/cockatrice/src/game/board/counter_state.h index 4c7b34473..0f2f16b55 100644 --- a/cockatrice/src/game/board/counter_state.h +++ b/cockatrice/src/game/board/counter_state.h @@ -35,23 +35,10 @@ public: return value; } - /** - * @brief Set the counter value. - * @param newValue The new value. - * @param skipDamageAnimation When true, valueChanged is emitted with skipDamageAnimation=true, letting views - * suppress damage-related feedback (e.g. battlefield shimmer, life counter flash) for values set during replay - * rewinds. - */ - void setValue(int newValue, bool skipDamageAnimation = false); + void setValue(int newValue); signals: - /** - * @brief Emitted whenever the value changes. - * @param oldValue The previous value. - * @param newValue The new value. - * @param skipDamageAnimation True when the change should not trigger damage/life-change feedback in views. - */ - void valueChanged(int oldValue, int newValue, bool skipDamageAnimation); + void valueChanged(int oldValue, int newValue); private: int id; diff --git a/cockatrice/src/game/game_event_handler.cpp b/cockatrice/src/game/game_event_handler.cpp index f146cdbb4..bc68d4d7c 100644 --- a/cockatrice/src/game/game_event_handler.cpp +++ b/cockatrice/src/game/game_event_handler.cpp @@ -430,13 +430,12 @@ void GameEventHandler::eventJoin(const Event_Join &event, int /*eventPlayerId*/, QString playerName = QString::fromStdString(playerInfo.user_info().name()); emit addPlayerToAutoCompleteList(playerName); - PlayerManager *playerManager = game->getPlayerManager(); - if (playerManager->getPlayers().contains(playerId) || playerManager->getSpectators().contains(playerId)) { + if (game->getPlayerManager()->getPlayers().contains(playerId)) { return; } if (playerInfo.spectator()) { - playerManager->addSpectator(playerId, playerInfo); + game->getPlayerManager()->addSpectator(playerId, playerInfo); emit logJoinSpectator(playerName); emit spectatorJoined(playerInfo); } else { diff --git a/cockatrice/src/game/player/event_processing_options.h b/cockatrice/src/game/player/event_processing_options.h index 06238d77e..4c7663789 100644 --- a/cockatrice/src/game/player/event_processing_options.h +++ b/cockatrice/src/game/player/event_processing_options.h @@ -13,8 +13,7 @@ enum EventProcessingOption { SKIP_REVEAL_WINDOW = 0x0001, - SKIP_TAP_ANIMATION = 0x0002, - SKIP_DAMAGE_ANIMATION = 0x0004 + SKIP_TAP_ANIMATION = 0x0002 }; // Wrap it in a QFlags typedef diff --git a/cockatrice/src/game/player/player_event_handler.cpp b/cockatrice/src/game/player/player_event_handler.cpp index 277b8b1d4..bc48298f7 100644 --- a/cockatrice/src/game/player/player_event_handler.cpp +++ b/cockatrice/src/game/player/player_event_handler.cpp @@ -262,15 +262,14 @@ void PlayerEventHandler::eventCreateCounter(const Event_CreateCounter &event) player->addCounter(event.counter_info()); } -void PlayerEventHandler::eventSetCounter(const Event_SetCounter &event, EventProcessingOptions options) +void PlayerEventHandler::eventSetCounter(const Event_SetCounter &event) { CounterState *ctr = player->getCounters().value(event.counter_id(), nullptr); if (!ctr) { return; } int oldValue = ctr->getValue(); - const bool skipDamageAnimation = options.testFlag(SKIP_DAMAGE_ANIMATION); - ctr->setValue(event.value(), skipDamageAnimation); + ctr->setValue(event.value()); emit logSetCounter(player, ctr->getName(), event.value(), oldValue); } @@ -626,7 +625,7 @@ void PlayerEventHandler::processGameEvent(GameEvent::GameEventType type, eventCreateCounter(event.GetExtension(Event_CreateCounter::ext)); break; case GameEvent::SET_COUNTER: - eventSetCounter(event.GetExtension(Event_SetCounter::ext), options); + eventSetCounter(event.GetExtension(Event_SetCounter::ext)); break; case GameEvent::DEL_COUNTER: eventDelCounter(event.GetExtension(Event_DelCounter::ext)); diff --git a/cockatrice/src/game/player/player_event_handler.h b/cockatrice/src/game/player/player_event_handler.h index 300cacd08..48ad85e88 100644 --- a/cockatrice/src/game/player/player_event_handler.h +++ b/cockatrice/src/game/player/player_event_handler.h @@ -153,7 +153,7 @@ public: void eventCreateCounter(const Event_CreateCounter &event); /// Set a player-level counter value. - void eventSetCounter(const Event_SetCounter &event, EventProcessingOptions options); + void eventSetCounter(const Event_SetCounter &event); /// Delete a player-level counter. void eventDelCounter(const Event_DelCounter &event); diff --git a/cockatrice/src/game/player/player_logic.cpp b/cockatrice/src/game/player/player_logic.cpp index 143df5c57..45ba09aac 100644 --- a/cockatrice/src/game/player/player_logic.cpp +++ b/cockatrice/src/game/player/player_logic.cpp @@ -175,15 +175,7 @@ void PlayerLogic::processPlayerInfo(const ServerInfo_Player &info) const ServerInfo_Card &cardInfo = zoneInfo.card_list(j); auto *card = new CardItem(this); card->processCardInfo(cardInfo); - // Zones without coordinates (hand, piles, stack) preserve the order - // they arrive in on the server in the positions of their cards list. - // The x coordinate of such cards is always 0, so inserting at it - // would reverse the list on reconnect. Append instead. - if (zoneInfo.with_coords()) { - zone->addCard(card, false, cardInfo.x(), cardInfo.y()); - } else { - zone->addCard(card, false, -1); - } + zone->addCard(card, false, cardInfo.x(), cardInfo.y()); } } if (zoneInfo.has_always_reveal_top_card()) { diff --git a/cockatrice/src/game/player/player_manager.cpp b/cockatrice/src/game/player/player_manager.cpp index 8486efbeb..6772d3ff1 100644 --- a/cockatrice/src/game/player/player_manager.cpp +++ b/cockatrice/src/game/player/player_manager.cpp @@ -75,14 +75,6 @@ PlayerLogic *PlayerManager::getPlayer(int playerId) const return player; } -void PlayerManager::clearSpectators() -{ - const QList spectatorIds = spectators.keys(); - for (int spectatorId : spectatorIds) { - removeSpectator(spectatorId); - } -} - void PlayerManager::onPlayerConceded(int playerId, bool conceded) { // Everything else cares about this diff --git a/cockatrice/src/game/player/player_manager.h b/cockatrice/src/game/player/player_manager.h index 504e65396..2f8b87af8 100644 --- a/cockatrice/src/game/player/player_manager.h +++ b/cockatrice/src/game/player/player_manager.h @@ -100,9 +100,6 @@ public: emit spectatorRemoved(spectatorId, spectatorInfo); } - /** @brief Remove all spectators, emitting the removal signal for each. */ - void clearSpectators(); - [[nodiscard]] AbstractGame *getGame() const { return game; 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/game_graphics/board/abstract_counter.cpp b/cockatrice/src/game_graphics/board/abstract_counter.cpp index 4ba04804f..e63117e13 100644 --- a/cockatrice/src/game_graphics/board/abstract_counter.cpp +++ b/cockatrice/src/game_graphics/board/abstract_counter.cpp @@ -29,9 +29,9 @@ AbstractCounter::AbstractCounter(CounterState *state, { setAcceptHoverEvents(true); - connect(state, &CounterState::valueChanged, this, [this](int oldValue, int newValue, bool skipDamageAnimation) { + connect(state, &CounterState::valueChanged, this, [this](int oldValue, int newValue) { value = newValue; - onValueChanged(oldValue, newValue, skipDamageAnimation); + onValueChanged(oldValue, newValue); update(); }); @@ -230,7 +230,7 @@ void AbstractCounterDialog::changeValue(int diff) setTextValue(QString::number(curValue)); } -void AbstractCounter::onValueChanged(int /*oldValue*/, int /*newValue*/, bool /*skipDamageAnimation*/) +void AbstractCounter::onValueChanged(int /*oldValue*/, int /*newValue*/) { // Default: no feedback. Subclasses such as PlayerCounter override this to // flash the counter on meaningful changes (life gain/loss). diff --git a/cockatrice/src/game_graphics/board/abstract_counter.h b/cockatrice/src/game_graphics/board/abstract_counter.h index 67b5b4074..9ddcc6d58 100644 --- a/cockatrice/src/game_graphics/board/abstract_counter.h +++ b/cockatrice/src/game_graphics/board/abstract_counter.h @@ -39,9 +39,8 @@ protected: * @brief Hook for subclasses that need per-value-change feedback (e.g. life-total flash). * * Called whenever the counter's value changes, before the item repaints. - * @param skipDamageAnimation True when damage-related feedback should be suppressed (replay rewinds). */ - virtual void onValueChanged(int oldValue, int newValue, bool skipDamageAnimation); + virtual void onValueChanged(int oldValue, int newValue); void mousePressEvent(QGraphicsSceneMouseEvent *event) override; void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; diff --git a/cockatrice/src/game_graphics/board/card_item.cpp b/cockatrice/src/game_graphics/board/card_item.cpp index c2dc455cc..c40c8c214 100644 --- a/cockatrice/src/game_graphics/board/card_item.cpp +++ b/cockatrice/src/game_graphics/board/card_item.cpp @@ -316,7 +316,7 @@ void CardItem::drawAttachArrow() for (const auto &item : scene()->selectedItems()) { CardItem *card = qgraphicsitem_cast(item); - if (card == nullptr || card == this) { + if (card == nullptr) { continue; } if (card->getZone() != state->getZone()) { diff --git a/cockatrice/src/game_graphics/game_scene.cpp b/cockatrice/src/game_graphics/game_scene.cpp index 17af7618b..457f1b3f7 100644 --- a/cockatrice/src/game_graphics/game_scene.cpp +++ b/cockatrice/src/game_graphics/game_scene.cpp @@ -221,12 +221,7 @@ void GameScene::removePlayer(PlayerLogic *player) clearArrowsForPlayer(player->getPlayerInfo()->getId()); - // Closing a view removes it from zoneViews synchronously, so iterate over a - // copy: otherwise a player with several open views (e.g. library and hand) - // only has the first one closed here and the remaining views are left - // pointing at a player that is about to be deleted. - const QList zoneViewCopy = zoneViews; - for (ZoneViewWidget *zone : zoneViewCopy) { + for (ZoneViewWidget *zone : zoneViews) { if (zone->getPlayer() == player) { zone->close(); } @@ -669,10 +664,7 @@ CardItem *GameScene::findTopmostCardInZone(const QList &items, */ void GameScene::toggleZoneView(PlayerLogic *player, const QString &zoneName, int numberCards, bool isReversed) { - // Closing a view removes it from zoneViews synchronously, so iterate over a - // copy to make sure every already-open matching view is closed. - const QList zoneViewCopy = zoneViews; - for (auto *view : zoneViewCopy) { + for (auto &view : zoneViews) { ZoneViewZone *temp = view->getZone(); if (temp->getLogic()->getName() == zoneName && temp->getLogic()->getPlayer() == player && qobject_cast(temp->getLogic())->getNumberCards() == numberCards) { diff --git a/cockatrice/src/game_graphics/hand_counter.cpp b/cockatrice/src/game_graphics/hand_counter.cpp index 8dcbcfdaa..35989ff38 100644 --- a/cockatrice/src/game_graphics/hand_counter.cpp +++ b/cockatrice/src/game_graphics/hand_counter.cpp @@ -1,6 +1,5 @@ #include "hand_counter.h" -#include "../interface/pixel_map_generator.h" #include "zones/card_zone.h" #include @@ -33,8 +32,7 @@ void HandCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*op QSize translatedSize = painter->combinedTransform().mapRect(boundingRect()).size().toSize(); QPixmap cachedPixmap; if (!QPixmapCache::find("handCounter" + QString::number(translatedSize.width()), &cachedPixmap)) { - cachedPixmap = - themePixmap(QStringLiteral("hand")).scaled(translatedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + cachedPixmap = QPixmap("theme:hand").scaled(translatedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); QPixmapCache::insert("handCounter" + QString::number(translatedSize.width()), cachedPixmap); } resetPainterTransform(painter); diff --git a/cockatrice/src/game_graphics/player/player_graphics_item.cpp b/cockatrice/src/game_graphics/player/player_graphics_item.cpp index 122ab83be..8bf2703e1 100644 --- a/cockatrice/src/game_graphics/player/player_graphics_item.cpp +++ b/cockatrice/src/game_graphics/player/player_graphics_item.cpp @@ -3,7 +3,6 @@ #include "../../game/player/player_actions.h" #include "../../interface/card_picture_loader/card_picture_loader.h" #include "../../interface/widgets/cards/art_crop_attribution.h" -#include "../../interface/widgets/cards/card_art_utils.h" #include "../../interface/widgets/playmat/playmat_utils.h" #include "../../interface/widgets/tabs/tab_game.h" #include "../board/abstract_card_item.h" @@ -252,8 +251,8 @@ void PlayerGraphicsItem::onCounterAdded(CounterState *state) AbstractCounter *widget; if (state->getName() == "life") { widget = playerTarget->addCounter(state); - connect(state, &CounterState::valueChanged, this, [this](int oldValue, int newValue, bool skipDamageAnimation) { - if (newValue < oldValue && !skipDamageAnimation) { + connect(state, &CounterState::valueChanged, this, [this](int oldValue, int newValue) { + if (newValue < oldValue) { tableZoneGraphicsItem->triggerDamageShimmer(); } }); @@ -443,7 +442,7 @@ void PlayerGraphicsItem::updatePlaymat() hasPlaymat = true; emit playmatChanged(true); } - playmatPixmap = CardArtUtils::rotateSidewaysLayoutArt(fullRes, card); + playmatPixmap = fullRes; update(); } diff --git a/cockatrice/src/game_graphics/player/player_list_widget.cpp b/cockatrice/src/game_graphics/player/player_list_widget.cpp index a7ec2e4a9..4268e1019 100644 --- a/cockatrice/src/game_graphics/player/player_list_widget.cpp +++ b/cockatrice/src/game_graphics/player/player_list_widget.cpp @@ -53,13 +53,13 @@ PlayerListWidget::PlayerListWidget(TabSupervisor *_tabSupervisor, QWidget *parent) : QTreeWidget(parent), tabSupervisor(_tabSupervisor), client(_client), game(_game), gameStarted(false) { - readyIcon = themePixmap(QStringLiteral("icons/ready_start")); - notReadyIcon = themePixmap(QStringLiteral("icons/not_ready_start")); - concededIcon = themePixmap(QStringLiteral("icons/conceded")); + readyIcon = QPixmap("theme:icons/ready_start"); + notReadyIcon = QPixmap("theme:icons/not_ready_start"); + concededIcon = QPixmap("theme:icons/conceded"); playerIcon = loadColorAdjustedPixmap("theme:icons/player"); judgeIcon = loadColorAdjustedPixmap("theme:icons/scales"); spectatorIcon = loadColorAdjustedPixmap("theme:icons/spectator"); - lockIcon = themePixmap(QStringLiteral("icons/lock")); + lockIcon = QPixmap("theme:icons/lock"); if (tabSupervisor) { itemDelegate = new PlayerListItemDelegate(this); @@ -92,11 +92,6 @@ void PlayerListWidget::retranslateUi() void PlayerListWidget::addPlayer(const ServerInfo_PlayerProperties &player) { - if (players.contains(player.player_id())) { - updatePlayerProperties(player); - return; - } - QTreeWidgetItem *newPlayer = new PlayerListTWI; players.insert(player.player_id(), newPlayer); updatePlayerProperties(player); @@ -181,17 +176,6 @@ void PlayerListWidget::removePlayer(int playerId) delete takeTopLevelItem(indexOfTopLevelItem(player)); } -void PlayerListWidget::clearSpectators() -{ - const QList playerIds = players.keys(); - for (int playerId : playerIds) { - QTreeWidgetItem *player = players.value(playerId, 0); - if (player && !player->data(1, Qt::UserRole).toBool()) { - removePlayer(playerId); - } - } -} - void PlayerListWidget::setActivePlayer(int playerId) { QMapIterator i(players); diff --git a/cockatrice/src/game_graphics/player/player_list_widget.h b/cockatrice/src/game_graphics/player/player_list_widget.h index f2f0be5fd..a53cfa989 100644 --- a/cockatrice/src/game_graphics/player/player_list_widget.h +++ b/cockatrice/src/game_graphics/player/player_list_widget.h @@ -66,7 +66,6 @@ public slots: void addPlayer(const ServerInfo_PlayerProperties &player); void removePlayer(int playerId); void updatePlayerProperties(const ServerInfo_PlayerProperties &prop, int playerId = -1); - void clearSpectators(); }; #endif diff --git a/cockatrice/src/game_graphics/player/player_target.cpp b/cockatrice/src/game_graphics/player/player_target.cpp index d6c28370d..910ee9c17 100644 --- a/cockatrice/src/game_graphics/player/player_target.cpp +++ b/cockatrice/src/game_graphics/player/player_target.cpp @@ -69,7 +69,7 @@ void PlayerCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /* } } -void PlayerCounter::onValueChanged(int oldValue, int newValue, bool skipDamageAnimation) +void PlayerCounter::onValueChanged(int oldValue, int newValue) { flashDelta = newValue - oldValue; if (flashDelta == 0) { @@ -81,11 +81,6 @@ void PlayerCounter::onValueChanged(int oldValue, int newValue, bool skipDamageAn return; } - if (skipDamageAnimation) { - flashAlpha = 0.0; - return; - } - flashAlpha = 1.0; flashClock.start(); if (scene()) { @@ -137,18 +132,8 @@ void PlayerTarget::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*o QRectF translatedRect = painter->combinedTransform().mapRect(avatarBoundingRect); QSize translatedSize = translatedRect.size().toSize(); QPixmap cachedPixmap; - // The key must cover everything the generated pawn depends on: the rendered - // size, the user level, and the pixmap being drawn. fullPixmap.cacheKey() is - // 0 for every null pixmap, so the default-pawn branch additionally needs the - // pawn's privlevel (lowercased, matching UserLevelPixmapGenerator) and colors - // in the key — otherwise two players without a custom avatar (and the same - // user level) would share one cached pawn. const QString cacheKey = "avatar" + QString::number(translatedSize.width()) + "_" + - QString::number(translatedSize.height()) + "_" + QString::number(info->user_level()) + - "_" + QString::number(fullPixmap.cacheKey()) + "_" + - QString::fromStdString(info->privlevel()).toLower() + "_" + - QString::fromStdString(info->pawn_colors().left_side()) + "_" + - QString::fromStdString(info->pawn_colors().right_side()); + QString::number(info->user_level()) + "_" + QString::number(fullPixmap.cacheKey()); if (!QPixmapCache::find(cacheKey, &cachedPixmap)) { cachedPixmap = QPixmap(translatedSize.width(), translatedSize.height()); diff --git a/cockatrice/src/game_graphics/player/player_target.h b/cockatrice/src/game_graphics/player/player_target.h index 1d06c6274..af0e9c8b7 100644 --- a/cockatrice/src/game_graphics/player/player_target.h +++ b/cockatrice/src/game_graphics/player/player_target.h @@ -21,7 +21,7 @@ class PlayerCounter : public AbstractCounter, public IAnimatedItem { Q_OBJECT protected: - void onValueChanged(int oldValue, int newValue, bool skipDamageAnimation) override; + void onValueChanged(int oldValue, int newValue) override; private: static constexpr qreal flashDurationMs = 450.0; diff --git a/cockatrice/src/game_graphics/zones/view_zone_widget.cpp b/cockatrice/src/game_graphics/zones/view_zone_widget.cpp index c13b79fea..17118e80d 100644 --- a/cockatrice/src/game_graphics/zones/view_zone_widget.cpp +++ b/cockatrice/src/game_graphics/zones/view_zone_widget.cpp @@ -62,7 +62,7 @@ ZoneViewWidget::ZoneViewWidget(PlayerLogic *_player, searchEdit.setPlaceholderText(tr("Search by card name (or search expressions)")); searchEdit.setClearButtonEnabled(true); searchEdit.addAction(loadColorAdjustedPixmap("theme:icons/search"), QLineEdit::LeadingPosition); - auto help = searchEdit.addAction(themePixmap(QStringLiteral("icons/info")), QLineEdit::TrailingPosition); + auto help = searchEdit.addAction(QPixmap("theme:icons/info"), QLineEdit::TrailingPosition); connect(help, &QAction::triggered, this, [this] { createSearchSyntaxHelpWindow(&searchEdit); }); @@ -549,7 +549,7 @@ void ZoneViewWidget::initStyleOption(QStyleOption *option) const { QStyleOptionTitleBar *titleBar = qstyleoption_cast(option); if (titleBar) { - titleBar->icon = themePixmap(QStringLiteral("cockatrice")); + titleBar->icon = QPixmap("theme:cockatrice"); } } 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..2f46e7941 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp @@ -1,14 +1,13 @@ #include "card_picture_loader.h" #include "../../client/settings/cache_settings.h" -#include "../pixel_map_generator.h" #include "card_picture_loader_cache_method.h" #include "card_picture_loader_local_schemes.h" #include #include #include -#include +#include #include #include #include @@ -20,7 +19,6 @@ #include #include #include -#include #include #include #include @@ -38,10 +36,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); @@ -66,7 +62,7 @@ void CardPictureLoader::getCardBackPixmap(QPixmap &pixmap, QSize size) QString backCacheKey = "_trice_card_back_" + QString::number(size.width()) + "x" + QString::number(size.height()); if (!QPixmapCache::find(backCacheKey, &pixmap)) { qCDebug(CardPictureLoaderLog) << "PictureLoader: cache miss for" << backCacheKey; - QPixmap tmpPixmap = themePixmap(QStringLiteral("cardback")); + QPixmap tmpPixmap("theme:cardback"); if (tmpPixmap.isNull()) { qCWarning(CardPictureLoaderLog) << "Failed to load 'theme:cardback'! Using fallback pixmap."; @@ -87,7 +83,7 @@ void CardPictureLoader::getCardBackLoadingInProgressPixmap(QPixmap &pixmap, QSiz "_trice_card_back_inprogress_" + QString::number(size.width()) + "x" + QString::number(size.height()); if (!QPixmapCache::find(backCacheKey, &pixmap)) { qCDebug(CardPictureLoaderCardBackCacheFailLog) << "PictureLoader: cache miss for" << backCacheKey; - QPixmap tmpPixmap = themePixmap(QStringLiteral("cardback")); + QPixmap tmpPixmap("theme:cardback"); if (tmpPixmap.isNull()) { qCWarning(CardPictureLoaderLog) << "Failed to load 'theme:cardback' for in-progress state! Using fallback."; @@ -109,7 +105,7 @@ void CardPictureLoader::getCardBackLoadingFailedPixmap(QPixmap &pixmap, QSize si "_trice_card_back_failed_" + QString::number(size.width()) + "x" + QString::number(size.height()); if (!QPixmapCache::find(backCacheKey, &pixmap)) { qCDebug(CardPictureLoaderCardBackCacheFailLog) << "PictureLoader: cache miss for" << backCacheKey; - QPixmap tmpPixmap = themePixmap(QStringLiteral("cardback")); + QPixmap tmpPixmap("theme:cardback"); if (tmpPixmap.isNull()) { qCWarning(CardPictureLoaderLog) << "Failed to load 'theme:cardback' for failed state! Using fallback."; @@ -142,8 +138,7 @@ void CardPictureLoader::getPixmap(QPixmap &pixmap, const ExactCard &card, QSize QPixmap bigPixmap; if (QPixmapCache::find(key, &bigPixmap)) { if (bigPixmap.isNull()) { - // Leave the pixmap null so callers fall back to a solid color - // instead of showing the card back. + getCardBackLoadingFailedPixmap(pixmap, size); QDateTime failedAtTime = getInstance().failedAt.value(key); if (!failedAtTime.isValid() || failedAtTime.addSecs(RETRY_FAILED_CARDS_SECS) < QDateTime::currentDateTime()) { @@ -209,49 +204,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 +264,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 +286,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 +325,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_loader_worker.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.cpp index 34092f361..d288236d2 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.cpp +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.cpp @@ -84,8 +84,8 @@ void CardPictureLoaderWorker::queueRequest(const QUrl &url, CardPictureLoaderWor SettingsCache::instance().cacheStorage().getCardPictureLoaderCacheMethod()) == CardPictureLoaderCacheMethod::CacheMethod::NETWORK_CACHE && cache->metaData(url).isValid()) { - // A request that will be served from the disk cache never touches the network and therefore - // doesn't use up any of the rate limit, so it gets to skip the queue. + // If we hit a cached url, we get to make the request for free, since it won't contribute towards the + // rate-limit makeRequest(url, worker); return; } @@ -107,13 +107,10 @@ QNetworkReply *CardPictureLoaderWorker::makeRequest(const QUrl &url, CardPicture req.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); req.setRawHeader("Accept", "image/avif,image/webp,image/apng,image/,/*;q=0.8"); - // Cached entries are served straight from the disk cache even when picture downloads are - // enabled: re-fetching an already-cached image would burn the rate limit for nothing. Only a - // genuine cache miss goes to the network, and only when downloads are enabled. - bool useNetworkCache = static_cast( - SettingsCache::instance().cacheStorage().getCardPictureLoaderCacheMethod()) == - CardPictureLoaderCacheMethod::CacheMethod::NETWORK_CACHE && - (cache->metaData(url).isValid() || !picDownload); + bool useNetworkCache = + !picDownload && static_cast( + SettingsCache::instance().cacheStorage().getCardPictureLoaderCacheMethod()) == + CardPictureLoaderCacheMethod::CacheMethod::NETWORK_CACHE; req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, useNetworkCache ? QNetworkRequest::AlwaysCache : QNetworkRequest::AlwaysNetwork); 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/deck_loader/deck_file_format.h b/cockatrice/src/interface/deck_loader/deck_file_format.h index 3a25797ec..995de32c0 100644 --- a/cockatrice/src/interface/deck_loader/deck_file_format.h +++ b/cockatrice/src/interface/deck_loader/deck_file_format.h @@ -17,7 +17,7 @@ enum Format PlainText, /** - * This is Cockatrice's native deck file format, and supports deck metadata such as banner cards and tags. + * This is cockatrice's native deck file format, and supports deck metadata such as banner cards and tags. * Stored as .cod files. */ Cockatrice diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp index f29b4eed2..f03339da8 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.cpp +++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp @@ -50,7 +50,7 @@ DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bo result = deckList.loadFromFile_Native(&file); if (!result) { qCInfo(DeckLoaderLog) << "Failed to load " << fileName - << "as Cockatrice format; retrying as plain format"; + << "as cockatrice format; retrying as plain format"; file.seek(0); result = deckList.loadFromFile_Plain(&file, CardNameNormalizer()); fmt = DeckFileFormat::PlainText; diff --git a/cockatrice/src/interface/deck_loader/deck_loader.h b/cockatrice/src/interface/deck_loader/deck_loader.h index be0df311d..b851c6895 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.h +++ b/cockatrice/src/interface/deck_loader/deck_loader.h @@ -131,7 +131,7 @@ public: static void printDeckList(QPrinter *printer, const DeckList &deckList); /** - * Converts the given deck's file to the Cockatrice file format. + * Converts the given deck's file to the cockatrice file format. * Uses the lastLoadInfo in the LoadedDeck to determine the current name of the file and where to save to. * @param deck The deck to convert. Should have valid lastLoadInfo. Will update the lastLoadInfo. * @return Whether the conversion succeeded. 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..9b8c4bcdc 100644 --- a/cockatrice/src/interface/pixel_map_generator.cpp +++ b/cockatrice/src/interface/pixel_map_generator.cpp @@ -1,7 +1,5 @@ #include "pixel_map_generator.h" -#include "theme_manager.h" - #include #include #include @@ -84,13 +82,7 @@ static QPixmap loadSvg(const QString &svgPath, const QSize &size, bool expandOnl /** * Try to load path image from non-SVG formats, otherwise fall back to SVG. * This is to allow custom themes to support non-SVG format type overrides, since SVG requires custom loading. - * - * The path may already carry the resolved file extension (e.g. via - * ThemeManager::assetPath); such paths are loaded directly. Otherwise a - * format-agnostic lookup probes png, jpg and finally svg. - * - * @param path The path to the file, with no file extension unless the caller - * already resolved it. File formats will be automatically detected. + * @param path The path to the file, with no file extension. File formats will be automatically detected. * @param size The desired size of the pixmap. * @param expandOnly If true, then keep the size of the initial pixmap to at least the size (Only relevant if SVG). * @@ -98,19 +90,6 @@ static QPixmap loadSvg(const QString &svgPath, const QSize &size, bool expandOnl */ static QPixmap tryLoadImage(const QString &path, const QSize &size, bool expandOnly = false) { - if (path.endsWith(QLatin1String(".svg"), Qt::CaseInsensitive)) { - return loadSvg(path, size, expandOnly); - } - if (path.endsWith(QLatin1String(".png"), Qt::CaseInsensitive) || - path.endsWith(QLatin1String(".jpg"), Qt::CaseInsensitive) || - path.endsWith(QLatin1String(".jpeg"), Qt::CaseInsensitive)) { - QPixmap pix(path); - if (!pix.isNull()) { - return pix.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation); - } - return {}; - } - const auto formats = {"png", "jpg"}; QPixmap returnPixmap; @@ -132,8 +111,7 @@ QPixmap PhasePixmapGenerator::generatePixmap(int height, QString name) return pmCache.value(key); } - QPixmap pixmap = tryLoadImage(QStringLiteral("theme:") + themeManager->assetPath(QStringLiteral("phases/") + name), - QSize(height, height)); + QPixmap pixmap = tryLoadImage("theme:phases/" + name, QSize(height, height)); pmCache.insert(key, pixmap); return pixmap; @@ -361,10 +339,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()); } @@ -422,8 +396,7 @@ QPixmap LockPixmapGenerator::generatePixmap(int height) return pmCache.value(key); } - QPixmap pixmap = tryLoadImage(QStringLiteral("theme:") + themeManager->assetPath(QStringLiteral("icons/lock")), - QSize(height, height), true); + QPixmap pixmap = tryLoadImage("theme:icons/lock", QSize(height, height), true); pmCache.insert(key, pixmap); return pixmap; } @@ -438,8 +411,7 @@ QPixmap DropdownIconPixmapGenerator::generatePixmap(int height, bool expanded) } QString name = expanded ? "dropdown_expanded" : "dropdown_collapsed"; - QPixmap pixmap = tryLoadImage(QStringLiteral("theme:") + themeManager->assetPath(QStringLiteral("icons/") + name), - QSize(height, height), true); + QPixmap pixmap = tryLoadImage("theme:icons/" + name, QSize(height, height), true); pmCache.insert(key, pixmap); return pixmap; @@ -500,13 +472,6 @@ QHash ManaSymbolPixmapGenerator::scaledCache; QPixmap loadColorAdjustedPixmap(const QString &name) { - // Prefer an authored scheme-qualified variant when one exists for this asset. - const QString variant = themeManager->schemeVariantPath(QStringView(name).mid(QStringLiteral("theme:").size())); - if (!variant.isEmpty()) { - return QPixmap(QStringLiteral("theme:") + variant); - } - - // Legacy fallback: runtime-invert for dark mode when no authored variant. if (qApp->palette().windowText().color().lightness() > 200) { QImage img(name); img.invertPixels(); @@ -517,21 +482,3 @@ QPixmap loadColorAdjustedPixmap(const QString &name) return QPixmap(name); } } - -QPixmap themePixmap(QStringView prefix) -{ - const QString resolved = themeManager->assetPath(prefix); - return QPixmap(QStringLiteral("theme:") + resolved); -} - -void clearPixmapGeneratorCaches() -{ - PhasePixmapGenerator::clear(); - CounterPixmapGenerator::clear(); - PingPixmapGenerator::clear(); - CountryPixmapGenerator::clear(); - UserLevelPixmapGenerator::clear(); - LockPixmapGenerator::clear(); - DropdownIconPixmapGenerator::clear(); - ManaSymbolPixmapGenerator::clear(); -} diff --git a/cockatrice/src/interface/pixel_map_generator.h b/cockatrice/src/interface/pixel_map_generator.h index b6e822fd9..17720166a 100644 --- a/cockatrice/src/interface/pixel_map_generator.h +++ b/cockatrice/src/interface/pixel_map_generator.h @@ -156,15 +156,4 @@ public: QPixmap loadColorAdjustedPixmap(const QString &name); -// Loads a "theme:" asset (with no file extension in prefix), preferring the -// scheme-qualified variant (prefix-dark / prefix-light, resolved via -// ThemeManager::assetPath) and falling back to the plain asset. Callers load -// the returned path directly. Use for scheme-sensitive pixmaps like -// backgrounds, the card back, and the app logo. -QPixmap themePixmap(QStringView prefix); - -// Clears every PixmapGenerator's static cache so scheme variants are -// re-resolved when the active theme or color scheme changes. -void clearPixmapGeneratorCaches(); - #endif 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..e6b4b3c7f 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -1,13 +1,10 @@ #include "theme_manager.h" #include "../../client/settings/cache_settings.h" -#include "pixel_map_generator.h" #include #include #include -#include -#include #include #include #include @@ -22,7 +19,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 +93,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 +116,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); } } @@ -149,74 +140,11 @@ bool ThemeManager::isDarkMode(const QString &themeDirPath) const } } -QString ThemeManager::schemeVariantPath(QStringView prefix) const +bool ThemeManager::isBuiltInTheme() { - static const QStringList formats = {QStringLiteral(".png"), QStringLiteral(".jpg"), QStringLiteral(".jpeg"), - QStringLiteral(".svg")}; - const QString scheme = isDarkMode(currentThemePath) ? QStringLiteral("dark") : QStringLiteral("light"); - const QString variantStem = prefix.toString() + QLatin1Char('-') + scheme; + const auto themeName = SettingsCache::instance().getThemeName(); - for (const QString &format : formats) { - if (QFileInfo::exists(QStringLiteral("theme:") + variantStem + format)) { - return variantStem + format; - } - } - return QString(); -} - -QString ThemeManager::assetPath(QStringView prefix) const -{ - // Probe order mirrors tryLoadImage: a theme may override the default SVG - // with a raster of the same stem, so raster wins over SVG within a stem. - static const QStringList formats = {QStringLiteral(".png"), QStringLiteral(".jpg"), QStringLiteral(".jpeg"), - QStringLiteral(".svg")}; - - auto findExisting = [](const QString &stem) { - for (const QString &format : formats) { - if (QFileInfo::exists(QStringLiteral("theme:") + stem + format)) { - return stem + format; - } - } - return QString(); - }; - - // Prefer the scheme-qualified variant when it exists, else the plain - // asset as the super fallback. Both return the resolved path including - // its file extension so callers can load it directly. - const QString variant = schemeVariantPath(prefix); - if (!variant.isEmpty()) { - return variant; - } - const QString resolvedPlain = findExisting(prefix.toString()); - 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) -{ - 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; -} - -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 +169,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)) { @@ -250,7 +180,7 @@ QStringMap &ThemeManager::getAvailableThemes() } } - // Load themes from Cockatrice system dir + // load themes from cockatrice system dir dir.setPath(systemThemesBasePath()); for (QString themeName : dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) { @@ -265,7 +195,7 @@ QStringMap &ThemeManager::getAvailableThemes() QBrush ThemeManager::loadBrush(QString fileName, QColor fallbackColor) { QBrush brush; - QPixmap tmp = QPixmap("theme:" + assetPath(QStringLiteral("zones/") + fileName)); + QPixmap tmp = QPixmap("theme:zones/" + fileName); if (tmp.isNull()) { brush.setColor(fallbackColor); brush.setStyle(Qt::SolidPattern); @@ -279,7 +209,7 @@ QBrush ThemeManager::loadBrush(QString fileName, QColor fallbackColor) QBrush ThemeManager::loadExtraBrush(QString fileName, QBrush &fallbackBrush) { QBrush brush; - QPixmap tmp = QPixmap("theme:" + assetPath(QStringLiteral("zones/") + fileName)); + QPixmap tmp = QPixmap("theme:zones/" + fileName); if (tmp.isNull()) { brush = fallbackBrush; @@ -357,7 +287,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 +298,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 +329,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 +372,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 +384,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() @@ -494,19 +393,9 @@ void ThemeManager::themeChangedSlot() currentThemePath = dirPath; QDir dir(dirPath); - // CSS — prefer the scheme-qualified stylesheet (style-dark.css / - // style-light.css) when present, else the plain style.css as fallback. - if (!dirPath.isEmpty()) { - const QString scheme = isDarkMode(dirPath) ? QStringLiteral("dark") : QStringLiteral("light"); - const QString schemeCss = QFileInfo(QStringLiteral(STYLE_CSS_NAME)).completeBaseName() + QLatin1Char('-') + - scheme + QStringLiteral(".css"); - if (dir.exists(schemeCss)) { - qApp->setStyleSheet("file:///" + dir.absoluteFilePath(schemeCss)); - } else if (dir.exists(STYLE_CSS_NAME)) { - qApp->setStyleSheet("file:///" + dir.absoluteFilePath(STYLE_CSS_NAME)); - } else { - qApp->setStyleSheet(""); - } + // CSS + if (!dirPath.isEmpty() && dir.exists(STYLE_CSS_NAME)) { + qApp->setStyleSheet("file:///" + dir.absoluteFilePath(STYLE_CSS_NAME)); } else { qApp->setStyleSheet(""); } @@ -521,19 +410,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); @@ -568,7 +446,6 @@ void ThemeManager::themeChangedSlot() } QPixmapCache::clear(); - clearPixmapGeneratorCaches(); emit themeChanged(); } diff --git a/cockatrice/src/interface/theme_manager.h b/cockatrice/src/interface/theme_manager.h index aadb38ee9..79a1b6470 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; @@ -97,20 +87,6 @@ public: // Load/save per-scheme palette colors static PaletteConfig loadPaletteConfig(const QString &themeDirPath, const QString &colorScheme); static bool savePaletteConfig(const QString &themeDirPath, const QString &colorScheme, const PaletteConfig &cfg); - // Resolve prefix to a scheme-qualified "theme:" path. Existence is probed - // internally across the formats themes may ship (.png/.jpg/.svg), so - // callers load the returned path directly. Prefers "-" - // when a file exists at that stem, otherwise the plain "" as the - // super fallback. The resolved scheme covers explicit light/dark as well - // as OS-resolved "system". Returns the path with its file extension when a - // match is found; unqualified assets keep working unchanged. - QString assetPath(QStringView prefix) const; - // Like assetPath, but resolves only the scheme-qualified variant - // ("-.") and returns an empty string when no - // variant exists — it never falls back to the plain "" asset. - // Callers that must distinguish "no authored variant" (e.g. to keep a - // legacy runtime fallback alive) should use this instead of assetPath. - QString schemeVariantPath(QStringView prefix) const; // Load the theme's shipped default palette, falling back to the system // theme directory when it is absent from the resolved (user) directory. static PaletteConfig @@ -125,17 +101,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_art_utils.cpp b/cockatrice/src/interface/widgets/cards/card_art_utils.cpp deleted file mode 100644 index b26b73593..000000000 --- a/cockatrice/src/interface/widgets/cards/card_art_utils.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "card_art_utils.h" - -#include -#include - -namespace CardArtUtils -{ -QPixmap rotateSidewaysLayoutArt(const QPixmap &art, const ExactCard &card) -{ - if (!card.getInfo().getUiAttributes().landscapeOrientation) { - return art; - } - - QTransform transform; - transform.rotate(90); - return art.transformed(transform, Qt::SmoothTransformation); -} -} // namespace CardArtUtils \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/cards/card_art_utils.h b/cockatrice/src/interface/widgets/cards/card_art_utils.h deleted file mode 100644 index 5c331a12c..000000000 --- a/cockatrice/src/interface/widgets/cards/card_art_utils.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef CARD_ART_UTILS_H -#define CARD_ART_UTILS_H - -#include - -class ExactCard; - -namespace CardArtUtils -{ -/** - * @brief Rotates a card's art upright when its layout shows sideways. - * - * Sideways-layout cards (planes, sieges/battles, split cards) store their - * landscape artwork rotated 90° inside a portrait frame. Art-crop displays, - * playmat art, and the card-info picture must show such art upright before - * sampling or painting. Portrait cards are returned unchanged. - * - * @param art The card pixmap to orient. - * @param card The card describing the art orientation. - * @return @p art rotated 90° clockwise for sideways-layout cards, else @p art. - */ -QPixmap rotateSidewaysLayoutArt(const QPixmap &art, const ExactCard &card); -} // namespace CardArtUtils - -#endif // CARD_ART_UTILS_H \ No newline at end of file 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..79ae087d7 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_widget.cpp @@ -5,7 +5,6 @@ #include "../../../interface/card_picture_loader/card_picture_loader.h" #include "../../../interface/widgets/tabs/tab_supervisor.h" #include "../../window_main.h" -#include "card_art_utils.h" #include #include @@ -74,8 +73,6 @@ CardInfoPictureWidget::CardInfoPictureWidget(QWidget *parent, const bool _hoverT update(); }); - connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardLangChanged, this, - &CardInfoPictureWidget::updatePixmap); } /** @@ -196,7 +193,12 @@ void CardInfoPictureWidget::paintEvent(QPaintEvent *event) QPixmap transformedPixmap = resizedPixmap; // Default pixmap if (SettingsCache::instance().cardsDisplay().getAutoRotateSidewaysLayoutCards()) { - transformedPixmap = CardArtUtils::rotateSidewaysLayoutArt(resizedPixmap, exactCard); + if (exactCard.getInfo().getUiAttributes().landscapeOrientation) { + // Rotate pixmap 90 degrees to the left + QTransform transform; + transform.rotate(90); + transformedPixmap = resizedPixmap.transformed(transform, Qt::SmoothTransformation); + } } // Handle DPI scaling 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_analytics/abstract_analytics_panel_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/abstract_analytics_panel_widget.cpp index 483fc71b0..089abc5c8 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/abstract_analytics_panel_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/abstract_analytics_panel_widget.cpp @@ -1,6 +1,5 @@ #include "abstract_analytics_panel_widget.h" -#include "../../pixel_map_generator.h" #include "deck_list_statistics_analyzer.h" #include @@ -21,7 +20,7 @@ AbstractAnalyticsPanelWidget::AbstractAnalyticsPanelWidget(QWidget *parent, Deck // config button configureButton = new QPushButton(this); - configureButton->setIcon(themePixmap(QStringLiteral("icons/cogwheel"))); + configureButton->setIcon(QPixmap("theme:icons/cogwheel")); configureButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); connect(configureButton, &QPushButton::clicked, this, &AbstractAnalyticsPanelWidget::applyConfigFromDialog); bannerAndSettingsLayout->addWidget(configureButton, 0); diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp index c8bdacf0d..9da821813 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp @@ -28,7 +28,7 @@ DeckEditorDatabaseDisplayWidget::DeckEditorDatabaseDisplayWidget(QWidget *parent searchEdit->setPlaceholderText(tr("Search by card name (or search expressions)")); searchEdit->setClearButtonEnabled(true); searchEdit->addAction(loadColorAdjustedPixmap("theme:icons/search"), QLineEdit::LeadingPosition); - auto help = searchEdit->addAction(themePixmap(QStringLiteral("icons/info")), QLineEdit::TrailingPosition); + auto help = searchEdit->addAction(QPixmap("theme:icons/info"), QLineEdit::TrailingPosition); setFocusProxy(searchEdit); setFocusPolicy(Qt::ClickFocus); @@ -59,13 +59,13 @@ DeckEditorDatabaseDisplayWidget::DeckEditorDatabaseDisplayWidget(QWidget *parent &DeckEditorDatabaseDisplayWidget::onRelatedCardClicked); aAddCard = new QAction(QString(), this); - aAddCard->setIcon(themePixmap(QStringLiteral("icons/arrow_right_green"))); + aAddCard->setIcon(QPixmap("theme:icons/arrow_right_green")); connect(aAddCard, &QAction::triggered, this, &DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck); auto *tbAddCard = new QToolButton(this); tbAddCard->setDefaultAction(aAddCard); aAddCardToSideboard = new QAction(QString(), this); - aAddCardToSideboard->setIcon(themePixmap(QStringLiteral("icons/arrow_right_blue"))); + aAddCardToSideboard->setIcon(QPixmap("theme:icons/arrow_right_blue")); connect(aAddCardToSideboard, &QAction::triggered, this, &DeckEditorDatabaseDisplayWidget::actAddCardToSideboard); auto *tbAddCardToSideboard = new QToolButton(this); tbAddCardToSideboard->setDefaultAction(aAddCardToSideboard); 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 b0b31074c..e2175a358 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 @@ -2,7 +2,6 @@ #include "../../../client/settings/cache_settings.h" #include "../../../client/settings/shortcuts_settings.h" -#include "../../pixel_map_generator.h" #include "../playmat/playmat_settings_dialog.h" #include "../settings_page/user_interface_settings_page.h" #include "../tabs/api/commander_spellbook/commander_bracket_widget.h" @@ -193,25 +192,25 @@ void DeckEditorDeckDockWidget::createDeckDock() &DeckEditorDeckDockWidget::applyActiveGroupCriteria); aIncrement = new QAction(QString(), this); - aIncrement->setIcon(themePixmap(QStringLiteral("icons/increment"))); + aIncrement->setIcon(QPixmap("theme:icons/increment")); connect(aIncrement, &QAction::triggered, this, &DeckEditorDeckDockWidget::actIncrementSelection); auto *tbIncrement = new QToolButton(this); tbIncrement->setDefaultAction(aIncrement); aDecrement = new QAction(QString(), this); - aDecrement->setIcon(themePixmap(QStringLiteral("icons/decrement"))); + aDecrement->setIcon(QPixmap("theme:icons/decrement")); connect(aDecrement, &QAction::triggered, this, &DeckEditorDeckDockWidget::actDecrementSelection); auto *tbDecrement = new QToolButton(this); tbDecrement->setDefaultAction(aDecrement); aRemoveCard = new QAction(QString(), this); - aRemoveCard->setIcon(themePixmap(QStringLiteral("icons/remove_row"))); + aRemoveCard->setIcon(QPixmap("theme:icons/remove_row")); connect(aRemoveCard, &QAction::triggered, this, &DeckEditorDeckDockWidget::actRemoveCard); auto *tbRemoveCard = new QToolButton(this); tbRemoveCard->setDefaultAction(aRemoveCard); aSwapCard = new QAction(QString(), this); - aSwapCard->setIcon(themePixmap(QStringLiteral("icons/swap"))); + aSwapCard->setIcon(QPixmap("theme:icons/swap")); connect(aSwapCard, &QAction::triggered, this, &DeckEditorDeckDockWidget::actSwapSelection); auto *tbSwapCard = new QToolButton(this); tbSwapCard->setDefaultAction(aSwapCard); @@ -345,9 +344,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(); diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.cpp index c6b6e4416..2d4fb60e8 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.cpp @@ -4,7 +4,6 @@ #include "../../../client/settings/shortcuts_settings.h" #include "../../../filters/filter_builder.h" #include "../../../filters/filter_tree_model.h" -#include "../../pixel_map_generator.h" #include #include @@ -43,11 +42,11 @@ void DeckEditorFilterDockWidget::createFiltersDock() connect(filterBuilder, &FilterBuilder::add, filterModel, &FilterTreeModel::addFilter); aClearFilterOne = new QAction(QString(), this); - aClearFilterOne->setIcon(themePixmap(QStringLiteral("icons/decrement"))); + aClearFilterOne->setIcon(QPixmap("theme:icons/decrement")); connect(aClearFilterOne, &QAction::triggered, this, &DeckEditorFilterDockWidget::actClearFilterOne); aClearFilterAll = new QAction(QString(), this); - aClearFilterAll->setIcon(themePixmap(QStringLiteral("icons/clearsearch"))); + aClearFilterAll->setIcon(QPixmap("theme:icons/clearsearch")); connect(aClearFilterAll, &QAction::triggered, this, &DeckEditorFilterDockWidget::actClearFilterAll); auto *filterDelOne = new QToolButton(); diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp index c93f12b34..cef459752 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp @@ -1,6 +1,5 @@ #include "deck_list_history_manager_widget.h" -#include "../../pixel_map_generator.h" #include "deck_state_manager.h" DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckStateManager *_deckStateManager, @@ -11,7 +10,7 @@ DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckStateManager *_de layout = new QHBoxLayout(this); aUndo = new QAction(QString(), this); - aUndo->setIcon(themePixmap(QStringLiteral("icons/arrow_undo"))); + aUndo->setIcon(QPixmap("theme:icons/arrow_undo")); aUndo->setShortcut(QKeySequence::Undo); aUndo->setShortcutContext(Qt::ApplicationShortcut); connect(aUndo, &QAction::triggered, this, &DeckListHistoryManagerWidget::doUndo); @@ -20,7 +19,7 @@ DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckStateManager *_de undoButton->setDefaultAction(aUndo); aRedo = new QAction(QString(), this); - aRedo->setIcon(themePixmap(QStringLiteral("icons/arrow_redo"))); + aRedo->setIcon(QPixmap("theme:icons/arrow_redo")); aRedo->setShortcut(QKeySequence::Redo); aRedo->setShortcutContext(Qt::ApplicationShortcut); connect(aRedo, &QAction::triggered, this, &DeckListHistoryManagerWidget::doRedo); @@ -32,7 +31,7 @@ DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckStateManager *_de layout->addWidget(redoButton); historyButton = new SettingsButtonWidget(this); - historyButton->setButtonIcon(themePixmap(QStringLiteral("icons/arrow_history"))); + historyButton->setButtonIcon(QPixmap("theme:icons/arrow_history")); historyLabel = new QLabel(this); 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_connect.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_connect.cpp index 700012d45..aa8a916f8 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_connect.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_connect.cpp @@ -1,7 +1,6 @@ #include "dlg_connect.h" #include "../../../client/settings/cache_settings.h" -#include "../../pixel_map_generator.h" #include #include @@ -22,7 +21,7 @@ DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent) previousHosts = new QComboBox(this); btnDeleteServer = new QPushButton(this); - btnDeleteServer->setIcon(themePixmap(QStringLiteral("icons/remove_row"))); + btnDeleteServer->setIcon(QPixmap("theme:icons/remove_row")); btnDeleteServer->setToolTip(tr("Delete the currently selected saved server")); btnDeleteServer->setFixedWidth(30); @@ -30,7 +29,7 @@ DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent) hps = new HandlePublicServers(this); btnRefreshServers = new QPushButton(this); - btnRefreshServers->setIcon(themePixmap(QStringLiteral("icons/sync"))); + btnRefreshServers->setIcon(QPixmap("theme:icons/sync")); btnRefreshServers->setToolTip(tr("Refresh the server list with known public servers")); btnRefreshServers->setFixedWidth(30); @@ -100,7 +99,7 @@ DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent) updateDisplayInfo(previousHosts->currentText()); btnForgotPassword = new QPushButton(this); - btnForgotPassword->setIcon(themePixmap(QStringLiteral("icons/forgot_password"))); + btnForgotPassword->setIcon(QPixmap("theme:icons/forgot_password")); btnForgotPassword->setToolTip(tr("Reset Password")); btnForgotPassword->setFixedWidth(30); connect(btnForgotPassword, &QPushButton::released, this, &DlgConnect::actForgotPassword); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp index a56aa8e35..f249976c2 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp @@ -1,6 +1,5 @@ #include "dlg_edit_tokens.h" -#include "../../pixel_map_generator.h" #include "../interface/widgets/utility/get_text_with_max.h" #include @@ -91,10 +90,10 @@ DlgEditTokens::DlgEditTokens(QWidget *parent) : QDialog(parent), currentCard(nul &DlgEditTokens::tokenSelectionChanged); QAction *aAddToken = new QAction(tr("Add token"), this); - aAddToken->setIcon(themePixmap(QStringLiteral("icons/increment"))); + aAddToken->setIcon(QPixmap("theme:icons/increment")); connect(aAddToken, &QAction::triggered, this, &DlgEditTokens::actAddToken); QAction *aRemoveToken = new QAction(tr("Remove token"), this); - aRemoveToken->setIcon(themePixmap(QStringLiteral("icons/decrement"))); + aRemoveToken->setIcon(QPixmap("theme:icons/decrement")); connect(aRemoveToken, &QAction::triggered, this, &DlgEditTokens::actRemoveToken); auto *databaseToolBar = new QToolBar; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp index 556c270be..7c107eb2f 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp @@ -1,7 +1,6 @@ #include "dlg_manage_sets.h" #include "../../../client/settings/cache_settings.h" -#include "../../pixel_map_generator.h" #include "../interface/card_picture_loader/card_picture_loader.h" #include "../interface/widgets/utility/custom_line_edit.h" @@ -36,28 +35,28 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent) setsEditToolBar->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); aTop = new QAction(QString(), this); - aTop->setIcon(themePixmap(QStringLiteral("icons/arrow_top_green"))); + aTop->setIcon(QPixmap("theme:icons/arrow_top_green")); aTop->setToolTip(tr("Move selected set to the top")); aTop->setEnabled(false); connect(aTop, &QAction::triggered, this, &WndSets::actTop); setsEditToolBar->addAction(aTop); aUp = new QAction(QString(), this); - aUp->setIcon(themePixmap(QStringLiteral("icons/arrow_up_green"))); + aUp->setIcon(QPixmap("theme:icons/arrow_up_green")); aUp->setToolTip(tr("Move selected set up")); aUp->setEnabled(false); connect(aUp, &QAction::triggered, this, &WndSets::actUp); setsEditToolBar->addAction(aUp); aDown = new QAction(QString(), this); - aDown->setIcon(themePixmap(QStringLiteral("icons/arrow_down_green"))); + aDown->setIcon(QPixmap("theme:icons/arrow_down_green")); aDown->setToolTip(tr("Move selected set down")); aDown->setEnabled(false); connect(aDown, &QAction::triggered, this, &WndSets::actDown); setsEditToolBar->addAction(aDown); aBottom = new QAction(QString(), this); - aBottom->setIcon(themePixmap(QStringLiteral("icons/arrow_bottom_green"))); + aBottom->setIcon(QPixmap("theme:icons/arrow_bottom_green")); aBottom->setToolTip(tr("Move selected set to the bottom")); aBottom->setEnabled(false); connect(aBottom, &QAction::triggered, this, &WndSets::actBottom); @@ -67,7 +66,7 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent) searchField = new LineEditUnfocusable; searchField->setObjectName("searchEdit"); searchField->setPlaceholderText(tr("Search by set name, code, type, or release date")); - searchField->addAction(themePixmap(QStringLiteral("icons/search")), LineEditUnfocusable::LeadingPosition); + searchField->addAction(QPixmap("theme:icons/search"), LineEditUnfocusable::LeadingPosition); searchField->setClearButtonEnabled(true); setFocusProxy(searchField); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp index 6d022861c..6ae8c9adb 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp @@ -1,7 +1,6 @@ #include "dlg_register.h" #include "../../../client/settings/cache_settings.h" -#include "../../pixel_map_generator.h" #include "../server/handle_public_servers.h" #include "../server/user/user_info_connection.h" @@ -25,7 +24,7 @@ DlgRegister::DlgRegister(QWidget *parent) : QDialog(parent) previousHosts = new QComboBox(this); btnDeleteServer = new QPushButton(this); - btnDeleteServer->setIcon(themePixmap(QStringLiteral("icons/remove_row"))); + btnDeleteServer->setIcon(QPixmap("theme:icons/remove_row")); btnDeleteServer->setToolTip(tr("Delete the currently selected saved server")); btnDeleteServer->setFixedWidth(30); @@ -33,7 +32,7 @@ DlgRegister::DlgRegister(QWidget *parent) : QDialog(parent) hps = new HandlePublicServers(this); btnRefreshServers = new QPushButton(this); - btnRefreshServers->setIcon(themePixmap(QStringLiteral("icons/sync"))); + btnRefreshServers->setIcon(QPixmap("theme:icons/sync")); btnRefreshServers->setToolTip(tr("Refresh the server list with known public servers")); btnRefreshServers->setFixedWidth(30); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp index 581920dbc..883cfcd03 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp @@ -6,7 +6,6 @@ #include "dlg_settings.h" #include "../../../client/settings/cache_settings.h" -#include "../../pixel_map_generator.h" #include "../main.h" #include "../settings_page/appearance_settings_page.h" #include "../settings_page/deck_editor_settings_page.h" @@ -97,7 +96,7 @@ void DlgSettings::setupUi() // Search bar searchEdit = new QLineEdit; searchEdit->setClearButtonEnabled(true); - searchEdit->addAction(themePixmap(QStringLiteral("icons/search")), QLineEdit::LeadingPosition); + searchEdit->addAction(QPixmap("theme:icons/search"), QLineEdit::LeadingPosition); searchEdit->installEventFilter(this); connect(searchEdit, &QLineEdit::textChanged, this, &DlgSettings::onSearchTextChanged); @@ -133,7 +132,7 @@ void DlgSettings::setupUi() pagesWidget->addWidget(makeScrollable(userInterfacePage)); pagesWidget->addWidget(makeScrollable(deckEditorPage)); pagesWidget->addWidget(makeScrollable(storagePage)); - pagesWidget->addWidget(makeScrollable(messagesPage)); + pagesWidget->addWidget(messagesPage); pagesWidget->addWidget(soundPage); pagesWidget->addWidget(shortcutsPage); @@ -416,11 +415,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) @@ -483,13 +477,13 @@ void DlgSettings::closeEvent(QCloseEvent *event) case Invalid: loadErrorMessage = tr("Your card database is invalid.\n\n" "Cockatrice may not function correctly with an invalid database\n\n" - "You may need to rerun Oracle to update your card database.\n\n" + "You may need to rerun oracle to update your card database.\n\n" "Would you like to change your database location setting?"); break; case VersionTooOld: loadErrorMessage = tr("Your card database version is too old.\n\n" "This can cause problems loading card information or images\n\n" - "Usually this can be fixed by rerunning Oracle to to update your card database.\n\n" + "Usually this can be fixed by rerunning oracle to to update your card database.\n\n" "Would you like to change your database location setting?"); break; case NotLoaded: 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..10fcdcb43 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.cpp +++ b/cockatrice/src/interface/widgets/general/home_widget.cpp @@ -2,7 +2,6 @@ #include "../../../client/settings/cache_settings.h" #include "../../../interface/widgets/tabs/tab_supervisor.h" -#include "../../pixel_map_generator.h" #include "../../theme_manager.h" #include "../../window_main.h" #include "../cards/art_crop_attribution.h" @@ -11,8 +10,6 @@ #include "home_tab_button_color.h" #include -#include -#include #include #include #include @@ -23,7 +20,7 @@ #include HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor) - : QWidget(parent), tabSupervisor(_tabSupervisor), background(themePixmap(QStringLiteral("backgrounds/home"))) + : QWidget(parent), tabSupervisor(_tabSupervisor), background("theme:backgrounds/home"), overlay("theme:cockatrice") { layout = new QGridLayout(this); @@ -55,27 +52,16 @@ 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, &HomeWidget::updateButtonsToBackgroundColor); - // 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); @@ -88,7 +74,7 @@ void HomeWidget::initializeBackgroundFromSource() switch (backgroundSourceType) { case BackgroundSources::Theme: cardChangeTimer->stop(); - background = themePixmap(QStringLiteral("backgrounds/home")); + background = QPixmap("theme:backgrounds/home"); backgroundSourceDeck = DeckList(); backgroundSourceCard->setCard(ExactCard()); updateButtonsToBackgroundColor(); @@ -114,24 +100,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 +229,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 +360,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 +431,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/first_run_wizard.cpp b/cockatrice/src/interface/widgets/onboarding/first_run_wizard.cpp index 4af02fe4f..618ac6f26 100644 --- a/cockatrice/src/interface/widgets/onboarding/first_run_wizard.cpp +++ b/cockatrice/src/interface/widgets/onboarding/first_run_wizard.cpp @@ -182,13 +182,6 @@ void FirstRunWizard::onCardDatabaseUpdateFinished(bool success) } } -void FirstRunWizard::onCardDatabaseUpdateProgress(const QString &stage, qint64 done, qint64 total) -{ - if (cardDatabasePage) { - cardDatabasePage->onUpdateProgress(stage, done, total); - } -} - void FirstRunWizard::finish() { accept(); diff --git a/cockatrice/src/interface/widgets/onboarding/first_run_wizard.h b/cockatrice/src/interface/widgets/onboarding/first_run_wizard.h index 21d7b6e06..2c186ef95 100644 --- a/cockatrice/src/interface/widgets/onboarding/first_run_wizard.h +++ b/cockatrice/src/interface/widgets/onboarding/first_run_wizard.h @@ -37,9 +37,6 @@ public slots: /** @brief Forwarded from MainWindow once the background card database update process exits. */ void onCardDatabaseUpdateFinished(bool success); - /** @brief Forwarded from MainWindow while the background card database update process runs. */ - void onCardDatabaseUpdateProgress(const QString &stage, qint64 done, qint64 total); - protected: void closeEvent(QCloseEvent *event) override; void changeEvent(QEvent *event) override; diff --git a/cockatrice/src/interface/widgets/onboarding/pages/card_database_setup_page.cpp b/cockatrice/src/interface/widgets/onboarding/pages/card_database_setup_page.cpp index 50e8ff63d..12116de7a 100644 --- a/cockatrice/src/interface/widgets/onboarding/pages/card_database_setup_page.cpp +++ b/cockatrice/src/interface/widgets/onboarding/pages/card_database_setup_page.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include @@ -180,25 +179,6 @@ void CardDatabaseSetupPage::onUpdateFinished(bool success) } } -void CardDatabaseSetupPage::onUpdateProgress(const QString &stage, qint64 done, qint64 total) -{ - if (state != State::Running) { - return; - } - progressBar->setRange(0, total > 0 ? static_cast(qMin(total, INT_MAX)) : 0); - progressBar->setValue(static_cast(qMin(done, INT_MAX))); - if (total > 0) { - const int percent = static_cast((100.0 * done) / total); - if (stage == QLatin1String("download")) { - statusLabel->setText(tr("Downloading the card database (%1%)…").arg(percent)); - } else if (stage == QLatin1String("scan")) { - statusLabel->setText(tr("Parsing the card database (%1%)…").arg(percent)); - } else if (stage == QLatin1String("import")) { - statusLabel->setText(tr("Importing cards (%1%)…").arg(percent)); - } - } -} - QString CardDatabaseSetupPage::nextButtonText() const { return state == State::NotStarted ? tr("Download") : QString(); diff --git a/cockatrice/src/interface/widgets/onboarding/pages/card_database_setup_page.h b/cockatrice/src/interface/widgets/onboarding/pages/card_database_setup_page.h index 870e759ea..0461d11d5 100644 --- a/cockatrice/src/interface/widgets/onboarding/pages/card_database_setup_page.h +++ b/cockatrice/src/interface/widgets/onboarding/pages/card_database_setup_page.h @@ -30,7 +30,6 @@ public: void retranslateUi() override; void onUpdateFinished(bool success); - void onUpdateProgress(const QString &stage, qint64 done, qint64 total); signals: void updateRequested(); 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/playmat/playmat_settings_dialog.cpp b/cockatrice/src/interface/widgets/playmat/playmat_settings_dialog.cpp index 9459c5ea9..57706cf93 100644 --- a/cockatrice/src/interface/widgets/playmat/playmat_settings_dialog.cpp +++ b/cockatrice/src/interface/widgets/playmat/playmat_settings_dialog.cpp @@ -2,7 +2,6 @@ #include "../../card_picture_loader/card_picture_loader.h" #include "../cards/art_crop_attribution.h" -#include "../cards/card_art_utils.h" #include "../utility/completer_utils.h" #include "card_database_display_model.h" #include "card_database_model.h" @@ -277,7 +276,7 @@ void PlaymatSettingsDialog::reloadPreview() return; } - currentPixmap = CardArtUtils::rotateSidewaysLayoutArt(fullRes, card); + currentPixmap = fullRes; preview->setPixmap(currentPixmap); preview->setParams(currentParams); preview->setAttribution(buildArtAttribution(card)); 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/quick_settings/settings_button_widget.cpp b/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.cpp index 881c3e3ac..9c433ab5a 100644 --- a/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.cpp +++ b/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.cpp @@ -1,7 +1,5 @@ #include "settings_button_widget.h" -#include "../../pixel_map_generator.h" - #include #include #include @@ -10,7 +8,7 @@ SettingsButtonWidget::SettingsButtonWidget(QWidget *parent) : QWidget(parent), button(new QToolButton(this)), popup(new SettingsPopupWidget(nullptr)) { - button->setIcon(themePixmap(QStringLiteral("icons/cogwheel"))); + button->setIcon(QPixmap("theme:icons/cogwheel")); button->setCheckable(true); button->setFixedSize(32, 32); connect(button, &QToolButton::clicked, this, &SettingsButtonWidget::togglePopup); diff --git a/cockatrice/src/interface/widgets/replay/replay_manager.cpp b/cockatrice/src/interface/widgets/replay/replay_manager.cpp index c51b96b6c..a2c1e0ff0 100644 --- a/cockatrice/src/interface/widgets/replay/replay_manager.cpp +++ b/cockatrice/src/interface/widgets/replay/replay_manager.cpp @@ -142,10 +142,8 @@ void ReplayManager::processNewEvents(PlaybackMode playbackMode) } // backwards skip => always skip tap animation - // backwards skip => always skip damage animation (battlefield shimmer / life counter flash) if (playbackMode == BACKWARD_SKIP) { options |= SKIP_TAP_ANIMATION; - options |= SKIP_DAMAGE_ANIMATION; } emit eventReplayed(replay->event_list(currentEvent), options); diff --git a/cockatrice/src/interface/widgets/replay/replay_widget.cpp b/cockatrice/src/interface/widgets/replay/replay_widget.cpp index c92771f8c..6c85d950e 100644 --- a/cockatrice/src/interface/widgets/replay/replay_widget.cpp +++ b/cockatrice/src/interface/widgets/replay/replay_widget.cpp @@ -2,7 +2,6 @@ #include "../../../client/settings/cache_settings.h" #include "../../../client/settings/shortcuts_settings.h" -#include "../../pixel_map_generator.h" #include "../interface/widgets/tabs/tab_game.h" #include "replay_manager.h" #include "replay_quick_settings_widget.h" @@ -51,15 +50,15 @@ ReplayWidget::ReplayWidget(QWidget *parent, GameReplay *replay) replayPlayButton = new QToolButton; replayPlayButton->setIconSize(QSize(32, 32)); QIcon playButtonIcon = QIcon(); - playButtonIcon.addPixmap(themePixmap(QStringLiteral("replay/start")), QIcon::Normal, QIcon::Off); - playButtonIcon.addPixmap(themePixmap(QStringLiteral("replay/pause")), QIcon::Normal, QIcon::On); + playButtonIcon.addPixmap(QPixmap("theme:replay/start"), QIcon::Normal, QIcon::Off); + playButtonIcon.addPixmap(QPixmap("theme:replay/pause"), QIcon::Normal, QIcon::On); replayPlayButton->setIcon(playButtonIcon); replayPlayButton->setCheckable(true); connect(replayPlayButton, &QToolButton::toggled, this, &ReplayWidget::replayPlayButtonToggled); replayFastForwardButton = new QToolButton; replayFastForwardButton->setIconSize(QSize(32, 32)); - replayFastForwardButton->setIcon(themePixmap(QStringLiteral("replay/fastforward"))); + replayFastForwardButton->setIcon(QPixmap("theme:replay/fastforward")); replayFastForwardButton->setCheckable(true); connect(replayFastForwardButton, &QToolButton::toggled, this, &ReplayWidget::updateTimeScaleFactor); diff --git a/cockatrice/src/interface/widgets/server/game_selector.cpp b/cockatrice/src/interface/widgets/server/game_selector.cpp index a8bf54e91..f41002247 100644 --- a/cockatrice/src/interface/widgets/server/game_selector.cpp +++ b/cockatrice/src/interface/widgets/server/game_selector.cpp @@ -1,6 +1,5 @@ #include "game_selector.h" -#include "../../pixel_map_generator.h" #include "../interface/widgets/dialogs/dlg_create_game.h" #include "../interface/widgets/dialogs/dlg_filter_games.h" #include "../interface/widgets/tabs/tab_account.h" @@ -96,10 +95,10 @@ GameSelector::GameSelector(AbstractClient *_client, } filterButton = new QPushButton; - filterButton->setIcon(themePixmap(QStringLiteral("icons/search"))); + filterButton->setIcon(QPixmap("theme:icons/search")); connect(filterButton, &QPushButton::clicked, this, &GameSelector::actSetFilter); clearFilterButton = new QPushButton; - clearFilterButton->setIcon(themePixmap(QStringLiteral("icons/clearsearch"))); + clearFilterButton->setIcon(QPixmap("theme:icons/clearsearch")); bool filtersSetToDefault = showFilters && gameListProxyModel->areFilterParametersSetToDefaults(); clearFilterButton->setEnabled(!filtersSetToDefault); connect(clearFilterButton, &QPushButton::clicked, this, &GameSelector::actClearFilter); diff --git a/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.cpp b/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.cpp index d62bf81aa..1f034b767 100644 --- a/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.cpp +++ b/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.cpp @@ -1,7 +1,5 @@ #include "remote_replay_list_tree_widget.h" -#include "../../../pixel_map_generator.h" - #include #include #include @@ -39,7 +37,7 @@ RemoteReplayList_TreeModel::RemoteReplayList_TreeModel(AbstractClient *_client, QFileIconProvider fip; dirIcon = fip.icon(QFileIconProvider::Folder); fileIcon = fip.icon(QFileIconProvider::File); - lockIcon = themePixmap(QStringLiteral("icons/lock")); + lockIcon = QPixmap("theme:icons/lock"); } RemoteReplayList_TreeModel::~RemoteReplayList_TreeModel() diff --git a/cockatrice/src/interface/widgets/server/user/user_card_art_provider.cpp b/cockatrice/src/interface/widgets/server/user/user_card_art_provider.cpp index 2ba745715..3a1876fa1 100644 --- a/cockatrice/src/interface/widgets/server/user/user_card_art_provider.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_card_art_provider.cpp @@ -1,7 +1,6 @@ #include "user_card_art_provider.h" #include "../../../card_picture_loader/card_picture_loader.h" -#include "../../cards/card_art_utils.h" #include #include @@ -53,25 +52,16 @@ void UserCardArtProvider::requestCardArt(const QString &userName, const QString processQueue(); } -QPixmap UserCardArtProvider::cropCardArt(const QPixmap &fullRes, const ExactCard &card) +QPixmap UserCardArtProvider::cropCardArt(const QPixmap &fullRes) { - QPixmap source = fullRes; - - // Sideways-layout cards (plane, siege/battle, split) store their landscape - // artwork rotated 90° inside a portrait frame. Rotate it upright first so - // the crop below lands on the horizontal art, mirroring the way - // CardInfoPictureWidget displays these cards. - const bool landscape = card.getInfo().getUiAttributes().landscapeOrientation; - source = CardArtUtils::rotateSidewaysLayoutArt(source, card); - - const QSize sz = source.size(); + const QSize sz = fullRes.size(); const int marginX = sz.width() * 0.07; - const int topMargin = landscape ? sz.height() * 0.05 : sz.height() * 0.11; - const int bottomMargin = landscape ? sz.height() * 0.42 : sz.height() * 0.45; + const int topMargin = sz.height() * 0.11; + const int bottomMargin = sz.height() * 0.45; - const QRect artRect(marginX, topMargin, sz.width() - 2 * marginX, sz.height() - topMargin - bottomMargin); + const QRect foilRect(marginX, topMargin, sz.width() - 2 * marginX, sz.height() - topMargin - bottomMargin); - return source.copy(artRect.intersected(source.rect())); + return fullRes.copy(foilRect.intersected(fullRes.rect())); } void UserCardArtProvider::insertIntoCache(const QString &key, const QPixmap &pixmap) @@ -121,7 +111,7 @@ void UserCardArtProvider::processQueue() // Synchronous hit (already loaded/on disk) if (!fullRes.isNull()) { - insertIntoCache(key, cropCardArt(fullRes, card)); + insertIntoCache(key, cropCardArt(fullRes)); pending.remove(key); emit cardArtUpdated(userName); @@ -145,7 +135,7 @@ void UserCardArtProvider::processQueue() CardPictureLoader::getPixmap(fullRes, card, QSize(745, 1040)); if (!fullRes.isNull()) { - self->insertIntoCache(key, self->cropCardArt(fullRes, card)); + self->insertIntoCache(key, self->cropCardArt(fullRes)); } self->pending.remove(key); diff --git a/cockatrice/src/interface/widgets/server/user/user_card_art_provider.h b/cockatrice/src/interface/widgets/server/user/user_card_art_provider.h index e8283a891..2592237c4 100644 --- a/cockatrice/src/interface/widgets/server/user/user_card_art_provider.h +++ b/cockatrice/src/interface/widgets/server/user/user_card_art_provider.h @@ -6,7 +6,6 @@ #include #include #include -#include class UserCardArtProvider : public QObject { @@ -17,7 +16,7 @@ public: void requestCardArt(const QString &userName, const QString &cardName, const QString &providerId); const QMap &cache() const; - static QPixmap cropCardArt(const QPixmap &fullRes, const ExactCard &card); + static QPixmap cropCardArt(const QPixmap &fullRes); signals: void cardArtUpdated(const QString &userName); diff --git a/cockatrice/src/interface/widgets/server/user/user_card_settings_dialog.cpp b/cockatrice/src/interface/widgets/server/user/user_card_settings_dialog.cpp index d49e3d540..532112964 100644 --- a/cockatrice/src/interface/widgets/server/user/user_card_settings_dialog.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_card_settings_dialog.cpp @@ -560,7 +560,7 @@ void UserCardArtSettingsDialog::reloadPreview() return; } - currentPixmap = UserCardArtProvider::cropCardArt(fullRes, card); + currentPixmap = UserCardArtProvider::cropCardArt(fullRes); preview->setPixmap(currentPixmap); preview->setParams(currentParams); diff --git a/cockatrice/src/interface/widgets/server/user/user_context_menu.cpp b/cockatrice/src/interface/widgets/server/user/user_context_menu.cpp index 0d2267a63..8d5d423f6 100644 --- a/cockatrice/src/interface/widgets/server/user/user_context_menu.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_context_menu.cpp @@ -51,8 +51,6 @@ UserContextMenu::UserContextMenu(TabSupervisor *_tabSupervisor, QWidget *parent, aDemoteFromMod = new QAction(QString(), this); aPromoteToJudge = new QAction(QString(), this); aDemoteFromJudge = new QAction(QString(), this); - aPromoteToDeveloper = new QAction(QString(), this); - aDemoteFromDeveloper = new QAction(QString(), this); aGetAdminNotes = new QAction(QString(), this); aInvestigateUser = new QAction(QString(), this); @@ -78,8 +76,6 @@ void UserContextMenu::retranslateUi() aDemoteFromMod->setText(tr("Dem&ote user from moderator")); aPromoteToJudge->setText(tr("Promote user to &judge")); aDemoteFromJudge->setText(tr("Demote user from judge")); - aPromoteToDeveloper->setText(tr("Promote user to &developer")); - aDemoteFromDeveloper->setText(tr("Demote user from de&veloper")); aGetAdminNotes->setText(tr("View admin notes")); aInvestigateUser->setText(tr("Investigate user")); } @@ -272,7 +268,7 @@ void UserContextMenu::adjustMod_processUserResponse(const Response &resp, const const Command_AdjustMod &cmd = commandContainer.admin_command(0).GetExtension(Command_AdjustMod::ext); if (resp.response_code() == Response::RespOk) { - if (cmd.should_be_mod() || cmd.should_be_judge() || cmd.should_be_developer()) { + if (cmd.should_be_mod() || cmd.should_be_judge()) { QMessageBox::information(static_cast(parent()), tr("Success"), tr("Successfully promoted user.")); } else { @@ -280,7 +276,7 @@ void UserContextMenu::adjustMod_processUserResponse(const Response &resp, const } } else { - if (cmd.should_be_mod() || cmd.should_be_judge() || cmd.should_be_developer()) { + if (cmd.should_be_mod() || cmd.should_be_judge()) { QMessageBox::information(static_cast(parent()), tr("Failed"), tr("Failed to promote user.")); } else { QMessageBox::information(static_cast(parent()), tr("Failed"), tr("Failed to demote user.")); @@ -441,18 +437,9 @@ void UserContextMenu::showContextMenu(const QPoint &pos, (tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsAdmin)) { menu->addAction(aPromoteToJudge); } - - if (userLevel.testFlag(ServerInfo_User::IsDeveloper) && - (tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsAdmin)) { - menu->addAction(aDemoteFromDeveloper); - - } else if (userLevel.testFlag(ServerInfo_User::IsRegistered) && - (tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsAdmin)) { - menu->addAction(aPromoteToDeveloper); - } } aDetails->setEnabled(true); - aChat->setEnabled(anotherUser && online && !userListProxy->isUserIgnored(userName)); + aChat->setEnabled(anotherUser && online); aShowGames->setEnabled(online); aReport->setEnabled(anotherUser); aAddToBuddyList->setEnabled(anotherUser); @@ -468,10 +455,6 @@ void UserContextMenu::showContextMenu(const QPoint &pos, aInvestigateUser->setEnabled(anotherUser); aPromoteToMod->setEnabled(anotherUser); aDemoteFromMod->setEnabled(anotherUser); - aPromoteToJudge->setEnabled(anotherUser); - aDemoteFromJudge->setEnabled(anotherUser); - aPromoteToDeveloper->setEnabled(anotherUser); - aDemoteFromDeveloper->setEnabled(anotherUser); QAction *actionClicked = menu->exec(pos); if (actionClicked == nullptr) { @@ -506,8 +489,6 @@ void UserContextMenu::showContextMenu(const QPoint &pos, execAdjustMod(userName, actionClicked == aPromoteToMod); } else if (actionClicked == aPromoteToJudge || actionClicked == aDemoteFromJudge) { execAdjustJudge(userName, actionClicked == aPromoteToJudge); - } else if (actionClicked == aPromoteToDeveloper || actionClicked == aDemoteFromDeveloper) { - execAdjustDeveloper(userName, actionClicked == aPromoteToDeveloper); } else if (actionClicked == aBanHistory) { execBanHistory(userName); } else if (actionClicked == aWarnUser) { @@ -625,15 +606,7 @@ void UserContextMenu::execAddToIgnore(const QString &userName) Command_AddToList cmd; cmd.set_list("ignore"); cmd.set_user_name(userName.toStdString()); - PendingCommand *pend = client->prepareSessionCommand(cmd); - connect(pend, &PendingCommand::finished, this, - [this, userName](const Response &response, const CommandContainer &, const QVariant &) { - if (response.response_code() == Response::RespOk) { - QMessageBox::information(static_cast(parent()), tr("Ignore list"), - tr("%1 has been added to your ignore list.").arg(userName)); - } - }); - client->sendCommand(pend); + client->sendCommand(client->prepareSessionCommand(cmd)); } void UserContextMenu::execRemoveFromIgnore(const QString &userName) @@ -725,14 +698,4 @@ void UserContextMenu::execAdjustJudge(const QString &userName, bool shouldBeJudg PendingCommand *pend = client->prepareAdminCommand(cmd); connect(pend, &PendingCommand::finished, this, &UserContextMenu::adjustMod_processUserResponse); client->sendCommand(pend); -} - -void UserContextMenu::execAdjustDeveloper(const QString &userName, bool shouldBeDeveloper) -{ - Command_AdjustMod cmd; - cmd.set_user_name(userName.toStdString()); - cmd.set_should_be_developer(shouldBeDeveloper); - PendingCommand *pend = client->prepareAdminCommand(cmd); - connect(pend, &PendingCommand::finished, this, &UserContextMenu::adjustMod_processUserResponse); - client->sendCommand(pend); -} +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/server/user/user_context_menu.h b/cockatrice/src/interface/widgets/server/user/user_context_menu.h index 6abbc057a..f1ce931f8 100644 --- a/cockatrice/src/interface/widgets/server/user/user_context_menu.h +++ b/cockatrice/src/interface/widgets/server/user/user_context_menu.h @@ -45,7 +45,6 @@ private: QAction *aBan, *aBanHistory; QAction *aPromoteToMod, *aDemoteFromMod; QAction *aPromoteToJudge, *aDemoteFromJudge; - QAction *aPromoteToDeveloper, *aDemoteFromDeveloper; QAction *aWarnUser, *aWarnHistory; QAction *aGetAdminNotes; std::function()> gameInviteLinkProvider; @@ -124,7 +123,6 @@ public: void execInvestigateUser(const QString &userName); void execAdjustMod(const QString &userName, bool shouldBeMod); void execAdjustJudge(const QString &userName, bool shouldBeJudge); - void execAdjustDeveloper(const QString &userName, bool shouldBeDeveloper); private: void execInvite(const QString &userName, const GameInviteOption &option); diff --git a/cockatrice/src/interface/widgets/server/user/user_info_box.cpp b/cockatrice/src/interface/widgets/server/user/user_info_box.cpp index 3d89cecf5..875bdfb05 100644 --- a/cockatrice/src/interface/widgets/server/user/user_info_box.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_info_box.cpp @@ -122,8 +122,6 @@ void UserInfoBox::updateInfo(const ServerInfo_User &user) QString userLevelText; if (userLevel.testFlag(ServerInfo_User::IsAdmin)) { userLevelText = tr("Administrator"); - } else if (userLevel.testFlag(ServerInfo_User::IsDeveloper)) { - userLevelText = tr("Developer"); } else if (userLevel.testFlag(ServerInfo_User::IsModerator)) { userLevelText = tr("Moderator"); } else if (userLevel.testFlag(ServerInfo_User::IsRegistered)) { diff --git a/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp b/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp index 8be76eea0..fb610e814 100644 --- a/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp @@ -245,9 +245,6 @@ void UserInfoHeaderWidget::paintEvent(QPaintEvent *) if (level.testFlag(ServerInfo_User::IsAdmin)) { return QColor(245, 158, 11); } - if (level.testFlag(ServerInfo_User::IsDeveloper)) { - return QColor(185, 28, 28); - } if (level.testFlag(ServerInfo_User::IsModerator)) { return QColor(59, 130, 246); } @@ -303,8 +300,6 @@ void UserInfoHeaderWidget::paintEvent(QPaintEvent *) } badge; if (level.testFlag(ServerInfo_User::IsAdmin)) { badge = {"ADMIN", QColor(245, 158, 11)}; - } else if (level.testFlag(ServerInfo_User::IsDeveloper)) { - badge = {"DEV", QColor(185, 28, 28)}; } else if (level.testFlag(ServerInfo_User::IsModerator)) { badge = {"MOD", QColor(59, 130, 246)}; } else if (level.testFlag(ServerInfo_User::IsJudge)) { diff --git a/cockatrice/src/interface/widgets/server/user/user_list_painter.cpp b/cockatrice/src/interface/widgets/server/user/user_list_painter.cpp index 34a3d6ae1..5c65b090d 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_painter.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_list_painter.cpp @@ -49,8 +49,6 @@ QColor UserListPainter::getAccentColor(const UserLevelFlags &userLevel, bool onl if (userLevel.testFlag(ServerInfo_User::IsAdmin)) { accentColor = QColor(245, 158, 11); - } else if (userLevel.testFlag(ServerInfo_User::IsDeveloper)) { - accentColor = QColor(185, 28, 28); } else if (userLevel.testFlag(ServerInfo_User::IsModerator)) { accentColor = QColor(59, 130, 246); } else if (userLevel.testFlag(ServerInfo_User::IsJudge)) { @@ -301,8 +299,6 @@ QList UserListPainter::buildBadges(const UserLevelFlags if (userLevel.testFlag(ServerInfo_User::IsAdmin)) { badges << Badge{"ADMIN", QColor(245, 158, 11)}; - } else if (userLevel.testFlag(ServerInfo_User::IsDeveloper)) { - badges << Badge{"DEV", QColor(185, 28, 28)}; } else if (userLevel.testFlag(ServerInfo_User::IsModerator)) { badges << Badge{"MOD", QColor(59, 130, 246)}; } else if (userLevel.testFlag(ServerInfo_User::IsJudge)) { @@ -389,9 +385,9 @@ void UserListPainter::paint(QPainter *painter, const QString userName = QString::fromStdString(userInfo.name()); const QString privLevel = QString::fromStdString(userInfo.privlevel()); const QColor accentColor = getAccentColor(userLevel, online); - const bool hasRole = - userLevel.testFlag(ServerInfo_User::IsAdmin) || userLevel.testFlag(ServerInfo_User::IsDeveloper) || - userLevel.testFlag(ServerInfo_User::IsModerator) || userLevel.testFlag(ServerInfo_User::IsJudge); + const bool hasRole = userLevel.testFlag(ServerInfo_User::IsAdmin) || + userLevel.testFlag(ServerInfo_User::IsModerator) || + userLevel.testFlag(ServerInfo_User::IsJudge); const QRectF cardRect = QRectF(rect).adjusted(3, 2, -3, -2); const int cardRight = getCardRight(option, rect); diff --git a/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp b/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp index 1bb7c5288..a8c99c979 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp @@ -234,11 +234,9 @@ bool UserListTWI::operator<(const QTreeWidgetItem &other) const const auto &lhsUserLevelFlags = UserLevelFlags(data(0, Qt::UserRole).toInt()); const auto &rhsUserLevelFlags = UserLevelFlags(other.data(0, Qt::UserRole).toInt()); - // Admins, Developers & Mods need no additional comparison checks, just to see if they're an admin, a developer - // or a moderator + // Admins & Mods need no additional comparison checks, just to see if they're an admin or a moderator static const QList userLevelWithNoOtherPrefOrder = { - ServerInfo_User_UserLevelFlag_IsAdmin, ServerInfo_User_UserLevelFlag_IsDeveloper, - ServerInfo_User_UserLevelFlag_IsModerator}; + ServerInfo_User_UserLevelFlag_IsAdmin, ServerInfo_User_UserLevelFlag_IsModerator}; for (const auto &userLevelEntry : userLevelWithNoOtherPrefOrder) { if (lhsUserLevelFlags.testFlag(userLevelEntry) && lhsUserLevelFlags.testFlag(userLevelEntry) == rhsUserLevelFlags.testFlag(userLevelEntry)) { 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/deck_editor_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/deck_editor_settings_page.cpp index f3eac05b8..f425afe60 100644 --- a/cockatrice/src/interface/widgets/settings_page/deck_editor_settings_page.cpp +++ b/cockatrice/src/interface/widgets/settings_page/deck_editor_settings_page.cpp @@ -1,7 +1,6 @@ #include "deck_editor_settings_page.h" #include "../../../client/settings/cache_settings.h" -#include "../../pixel_map_generator.h" #include "update/card_spoiler/spoiler_background_updater.h" #include @@ -54,15 +53,15 @@ DeckEditorSettingsPage::DeckEditorSettingsPage() urlList->addItems(SettingsCache::instance().downloads().getAllURLs()); aAdd = new QAction(this); - aAdd->setIcon(themePixmap(QStringLiteral("icons/increment"))); + aAdd->setIcon(QPixmap("theme:icons/increment")); connect(aAdd, &QAction::triggered, this, &DeckEditorSettingsPage::actAddURL); aEdit = new QAction(this); - aEdit->setIcon(themePixmap(QStringLiteral("icons/pencil"))); + aEdit->setIcon(QPixmap("theme:icons/pencil")); connect(aEdit, &QAction::triggered, this, &DeckEditorSettingsPage::actEditURL); aRemove = new QAction(this); - aRemove->setIcon(themePixmap(QStringLiteral("icons/decrement"))); + aRemove->setIcon(QPixmap("theme:icons/decrement")); connect(aRemove, &QAction::triggered, this, &DeckEditorSettingsPage::actRemoveURL); auto *urlToolBar = new QToolBar; 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/settings_page/messages_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/messages_settings_page.cpp index a3b89f8c4..e4f24ab73 100644 --- a/cockatrice/src/interface/widgets/settings_page/messages_settings_page.cpp +++ b/cockatrice/src/interface/widgets/settings_page/messages_settings_page.cpp @@ -1,7 +1,6 @@ #include "messages_settings_page.h" #include "../../../client/settings/cache_settings.h" -#include "../../pixel_map_generator.h" #include "../interface/widgets/utility/get_text_with_max.h" #include @@ -60,10 +59,6 @@ MessagesSettingsPage::MessagesSettingsPage() connect(&roomHistory, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().chat(), &ChatSettings::setRoomHistory); - ignoreAllPrivateMessagesCheckBox.setChecked(SettingsCache::instance().chat().getIgnoreAllPrivateMessages()); - connect(&ignoreAllPrivateMessagesCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().chat(), - &ChatSettings::setIgnoreAllPrivateMessages); - customAlertString = new QLineEdit(); customAlertString->setText(SettingsCache::instance().chat().getHighlightWords()); connect(customAlertString, &QLineEdit::textChanged, &SettingsCache::instance().chat(), @@ -81,7 +76,6 @@ MessagesSettingsPage::MessagesSettingsPage() chatGrid->addWidget(&messagePopups, 5, 0); chatGrid->addWidget(&mentionPopups, 6, 0); chatGrid->addWidget(&roomHistory, 7, 0); - chatGrid->addWidget(&ignoreAllPrivateMessagesCheckBox, 8, 0); chatGroupBox = new QGroupBox; chatGroupBox->setLayout(chatGrid); @@ -108,15 +102,15 @@ MessagesSettingsPage::MessagesSettingsPage() } aAdd = new QAction(this); - aAdd->setIcon(themePixmap(QStringLiteral("icons/increment"))); + aAdd->setIcon(QPixmap("theme:icons/increment")); connect(aAdd, &QAction::triggered, this, &MessagesSettingsPage::actAdd); aEdit = new QAction(this); - aEdit->setIcon(themePixmap(QStringLiteral("icons/pencil"))); + aEdit->setIcon(QPixmap("theme:icons/pencil")); connect(aEdit, &QAction::triggered, this, &MessagesSettingsPage::actEdit); aRemove = new QAction(this); - aRemove->setIcon(themePixmap(QStringLiteral("icons/decrement"))); + aRemove->setIcon(QPixmap("theme:icons/decrement")); connect(aRemove, &QAction::triggered, this, &MessagesSettingsPage::actRemove); auto *messageToolBar = new QToolBar; @@ -262,7 +256,6 @@ void MessagesSettingsPage::retranslateUi() messagePopups.setText(tr("Enable desktop notifications for private messages")); mentionPopups.setText(tr("Enable desktop notification for mentions")); roomHistory.setText(tr("Enable room message history on join")); - ignoreAllPrivateMessagesCheckBox.setText(tr("Ignore all private messages")); hexLabel.setText(tr("(Color is hexadecimal)")); hexHighlightLabel.setText(tr("(Color is hexadecimal)")); customAlertStringLabel.setText(tr("Separate words with a space, alphanumeric characters only")); diff --git a/cockatrice/src/interface/widgets/settings_page/messages_settings_page.h b/cockatrice/src/interface/widgets/settings_page/messages_settings_page.h index 436ebbad9..e98ae0592 100644 --- a/cockatrice/src/interface/widgets/settings_page/messages_settings_page.h +++ b/cockatrice/src/interface/widgets/settings_page/messages_settings_page.h @@ -40,7 +40,6 @@ private: QCheckBox messagePopups; QCheckBox mentionPopups; QCheckBox roomHistory; - QCheckBox ignoreAllPrivateMessagesCheckBox; QGroupBox *chatGroupBox; QGroupBox *highlightGroupBox; QGroupBox *messageGroupBox; diff --git a/cockatrice/src/interface/widgets/settings_page/shortcut_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/shortcut_settings_page.cpp index 1277d0e49..1f1867f7c 100644 --- a/cockatrice/src/interface/widgets/settings_page/shortcut_settings_page.cpp +++ b/cockatrice/src/interface/widgets/settings_page/shortcut_settings_page.cpp @@ -3,7 +3,6 @@ #include "../../../client/settings/cache_settings.h" #include "../../../client/settings/shortcut_treeview.h" #include "../../../client/settings/shortcuts_settings.h" -#include "../../pixel_map_generator.h" #include "../interface/widgets/utility/custom_line_edit.h" #include "../interface/widgets/utility/sequence_edit.h" @@ -48,8 +47,8 @@ ShortcutSettingsPage::ShortcutSettingsPage() btnResetAll = new QPushButton(this); btnClearAll = new QPushButton(this); - btnResetAll->setIcon(themePixmap(QStringLiteral("icons/update"))); - btnClearAll->setIcon(themePixmap(QStringLiteral("icons/clearsearch"))); + btnResetAll->setIcon(QPixmap("theme:icons/update")); + btnClearAll->setIcon(QPixmap("theme:icons/clearsearch")); // layout auto *_editLayout = new QGridLayout; 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/api/archidekt/tab_archidekt.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp index 888197f53..98b21d0f1 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp @@ -1,7 +1,6 @@ #include "tab_archidekt.h" #include "../../../../../client/settings/cache_settings.h" -#include "../../../../pixel_map_generator.h" #include "../../../cards/additional_info/mana_symbol_widget.h" #include "../../../utility/completer_utils.h" #include "../../tab_supervisor.h" @@ -214,7 +213,7 @@ void TabArchidekt::setupFilterWidgets() // Format filter (collapsible) formatButton = new SettingsButtonWidget(secondaryToolbar); formatButton->setButtonText(tr("Formats")); - formatButton->setButtonIcon(themePixmap(QStringLiteral("icons/scale_balanced"))); + formatButton->setButtonIcon(QPixmap("theme:icons/scale_balanced")); QWidget *formatContainer = new QWidget(secondaryToolbar); QGridLayout *formatLayout = new QGridLayout(formatContainer); diff --git a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_widget.cpp index 2cbbedfa4..4f50e38a6 100644 --- a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_widget.cpp @@ -1,7 +1,6 @@ #include "commander_bracket_widget.h" #include "../../../../../client/settings/cache_settings.h" -#include "../../../../pixel_map_generator.h" #include "commander_bracket_service.h" #include @@ -31,7 +30,7 @@ CommanderBracketWidget::CommanderBracketWidget(QWidget *parent) : QWidget(parent bracketInfoButton->setEnabled(false); bracketRefreshButton = new QToolButton(this); - bracketRefreshButton->setIcon(themePixmap(QStringLiteral("icons/reload"))); + bracketRefreshButton->setIcon(QPixmap("theme:icons/reload")); bracketRefreshButton->setAutoRaise(true); connect(bracketRefreshButton, &QToolButton::clicked, this, &CommanderBracketWidget::requestBracketEstimate); diff --git a/cockatrice/src/interface/widgets/tabs/tab_account.cpp b/cockatrice/src/interface/widgets/tabs/tab_account.cpp index dbcf50966..410a48d40 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_account.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_account.cpp @@ -137,11 +137,6 @@ void TabAccount::retranslateUi() buddyList->retranslateUi(); ignoreList->retranslateUi(); userInfoBox->retranslateUi(); - - buddyList->setToolTip(tr("Buddies are marked with a star in chat, a sound plays when they join or leave the " - "server, and they can be invited to buddy-only games.")); - ignoreList->setToolTip(tr("Ignored users' chat messages are hidden from you, and they cannot send you private " - "messages or join your games.")); } void TabAccount::processListUsersResponse(const Response &response) diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp index f3535d850..49e42e4cf 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp @@ -2,7 +2,6 @@ #include "../../../client/settings/cache_settings.h" #include "../../deck_loader/deck_loader.h" -#include "../../pixel_map_generator.h" #include "../interface/widgets/server/remote/remote_decklist_tree_widget.h" #include "../interface/widgets/utility/get_text_with_max.h" @@ -106,19 +105,19 @@ TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, // Left side actions aOpenLocalDeck = new QAction(this); - aOpenLocalDeck->setIcon(themePixmap(QStringLiteral("icons/pencil"))); + aOpenLocalDeck->setIcon(QPixmap("theme:icons/pencil")); connect(aOpenLocalDeck, &QAction::triggered, this, &TabDeckStorage::actOpenLocalDeck); aRenameLocal = new QAction(this); - aRenameLocal->setIcon(themePixmap(QStringLiteral("icons/rename"))); + aRenameLocal->setIcon(QPixmap("theme:icons/rename")); connect(aRenameLocal, &QAction::triggered, this, &TabDeckStorage::actRenameLocal); aUpload = new QAction(this); - aUpload->setIcon(themePixmap(QStringLiteral("icons/arrow_right_green"))); + aUpload->setIcon(QPixmap("theme:icons/arrow_right_green")); connect(aUpload, &QAction::triggered, this, &TabDeckStorage::actUpload); aNewLocalFolder = new QAction(this); aNewLocalFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_FileDialogNewFolder)); connect(aNewLocalFolder, &QAction::triggered, this, &TabDeckStorage::actNewLocalFolder); aDeleteLocalDeck = new QAction(this); - aDeleteLocalDeck->setIcon(themePixmap(QStringLiteral("icons/remove_row"))); + aDeleteLocalDeck->setIcon(QPixmap("theme:icons/remove_row")); connect(aDeleteLocalDeck, &QAction::triggered, this, &TabDeckStorage::actDeleteLocalDeck); aOpenDecksFolder = new QAction(this); @@ -127,16 +126,16 @@ TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, // Right side actions aOpenRemoteDeck = new QAction(this); - aOpenRemoteDeck->setIcon(themePixmap(QStringLiteral("icons/pencil"))); + aOpenRemoteDeck->setIcon(QPixmap("theme:icons/pencil")); connect(aOpenRemoteDeck, &QAction::triggered, this, &TabDeckStorage::actOpenRemoteDeck); aDownload = new QAction(this); - aDownload->setIcon(themePixmap(QStringLiteral("icons/arrow_left_green"))); + aDownload->setIcon(QPixmap("theme:icons/arrow_left_green")); connect(aDownload, &QAction::triggered, this, &TabDeckStorage::actDownload); aNewFolder = new QAction(this); aNewFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_FileDialogNewFolder)); connect(aNewFolder, &QAction::triggered, this, &TabDeckStorage::actNewFolder); aDeleteRemoteDeck = new QAction(this); - aDeleteRemoteDeck->setIcon(themePixmap(QStringLiteral("icons/remove_row"))); + aDeleteRemoteDeck->setIcon(QPixmap("theme:icons/remove_row")); connect(aDeleteRemoteDeck, &QAction::triggered, this, &TabDeckStorage::actDeleteRemoteDeck); // Add actions to toolbars diff --git a/cockatrice/src/interface/widgets/tabs/tab_developer.cpp b/cockatrice/src/interface/widgets/tabs/tab_developer.cpp deleted file mode 100644 index 39652ef7d..000000000 --- a/cockatrice/src/interface/widgets/tabs/tab_developer.cpp +++ /dev/null @@ -1,261 +0,0 @@ -/** - * @file tab_developer.cpp - * @ingroup ServerTabs - */ -//! \todo Document this file. - -#include "tab_developer.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static constexpr int DEFAULT_AUTO_REFRESH_INTERVAL_SECS = 30; - -TabDeveloper::TabDeveloper(TabSupervisor *_tabSupervisor, AbstractClient *_client) - : Tab(_tabSupervisor), client(_client) -{ - statsTable = new QTableWidget(0, 2); - statsTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - statsTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - statsTable->setSelectionBehavior(QAbstractItemView::SelectRows); - statsTable->setSelectionMode(QAbstractItemView::SingleSelection); - statsTable->verticalHeader()->setVisible(false); - statsTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Interactive); - statsTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Interactive); - statsTable->horizontalHeader()->setStretchLastSection(true); - - commandTable = new QTableWidget(0, 4); - commandTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - commandTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - commandTable->setSelectionBehavior(QAbstractItemView::SelectRows); - commandTable->setSelectionMode(QAbstractItemView::SingleSelection); - commandTable->verticalHeader()->setVisible(false); - commandTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Interactive); - commandTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Interactive); - commandTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Interactive); - commandTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Interactive); - - statusLabel = new QLabel; - - autoRefreshCheckBox = new QCheckBox; - autoRefreshCheckBox->setChecked(false); - - refreshIntervalSpinBox = new QSpinBox; - refreshIntervalSpinBox->setRange(5, 3600); - refreshIntervalSpinBox->setValue(DEFAULT_AUTO_REFRESH_INTERVAL_SECS); - refreshIntervalSpinBox->setEnabled(false); - - autoRefreshTimer = new QTimer(this); - connect(autoRefreshTimer, &QTimer::timeout, this, &TabDeveloper::refreshClicked); - connect(autoRefreshCheckBox, &QCheckBox::toggled, this, &TabDeveloper::autoRefreshToggled); - connect(refreshIntervalSpinBox, QOverload::of(&QSpinBox::valueChanged), this, - &TabDeveloper::refreshIntervalChanged); - - refreshButton = new QPushButton; - refreshButton->setAutoDefault(true); - connect(refreshButton, &QPushButton::clicked, this, &TabDeveloper::refreshClicked); - - auto *buttonLayout = new QHBoxLayout; - buttonLayout->addWidget(statusLabel, 1, Qt::AlignLeft); - buttonLayout->addWidget(autoRefreshCheckBox, 0, Qt::AlignRight); - buttonLayout->addWidget(refreshIntervalSpinBox, 0, Qt::AlignRight); - buttonLayout->addWidget(refreshButton, 0, Qt::AlignRight); - - auto *tableLayout = new QHBoxLayout; - tableLayout->addWidget(statsTable, 1); - tableLayout->addWidget(commandTable, 2); - - auto *mainLayout = new QVBoxLayout; - mainLayout->addLayout(tableLayout, 1); - mainLayout->addLayout(buttonLayout); - - auto *central = new QWidget; - central->setLayout(mainLayout); - setCentralWidget(central); - - retranslateUi(); -} - -void TabDeveloper::retranslateUi() -{ - autoRefreshCheckBox->setText(tr("Auto-refresh")); - autoRefreshCheckBox->setToolTip(tr("Automatically request fresh server statistics at a fixed interval.")); - refreshIntervalSpinBox->setSuffix(tr(" s")); - refreshIntervalSpinBox->setToolTip(tr("Seconds between automatic refreshes.")); - refreshButton->setText(tr("Refresh server stats")); - statsTable->setHorizontalHeaderLabels(QString(tr("Statistic;Value")).split(";")); - commandTable->setHorizontalHeaderLabels(QString(tr("Command;Count;Total ms;Avg ms")).split(";")); - if (statsTable->rowCount() == 0) { - statusLabel->clear(); - } -} - -QString TabDeveloper::formatBytes(quint64 bytes) -{ - const quint64 kib = 1024; - const quint64 mib = 1024 * kib; - const quint64 gib = 1024 * mib; - if (bytes >= gib) { - return tr("%1 GiB").arg(QString::number(bytes / static_cast(gib), 'f', 2)); - } - if (bytes >= mib) { - return tr("%1 MiB").arg(QString::number(bytes / static_cast(mib), 'f', 2)); - } - if (bytes >= kib) { - return tr("%1 KiB").arg(QString::number(bytes / static_cast(kib), 'f', 2)); - } - return tr("%1 bytes").arg(bytes); -} - -QString TabDeveloper::formatDurationMs(qint64 ms) -{ - if (ms >= 1000) { - return tr("%1 s").arg(QString::number(ms / 1000.0, 'f', 2)); - } - return tr("%1 ms").arg(ms); -} - -void TabDeveloper::appendStatRow(const QString &name, const QString &value) -{ - const int row = statsTable->rowCount(); - statsTable->insertRow(row); - statsTable->setItem(row, 0, new QTableWidgetItem(name)); - statsTable->setItem(row, 1, new QTableWidgetItem(value)); -} - -void TabDeveloper::appendSeparatorRow(const QString §ionTitle) -{ - const int row = statsTable->rowCount(); - statsTable->insertRow(row); - auto *labelItem = new QTableWidgetItem(sectionTitle); - auto font = labelItem->font(); - font.setBold(true); - labelItem->setFont(font); - labelItem->setFlags(labelItem->flags() & ~Qt::ItemIsSelectable); - statsTable->setItem(row, 0, labelItem); - statsTable->setItem(row, 1, new QTableWidgetItem(QString())); -} - -void TabDeveloper::refreshClicked() -{ - if (requestPending) { - return; - } - requestPending = true; - Command_GetServerStats cmd; - PendingCommand *pend = client->prepareDeveloperCommand(cmd); - connect(pend, &PendingCommand::finished, this, &TabDeveloper::serverStatsResponse); - client->sendCommand(pend); -} - -void TabDeveloper::autoRefreshToggled(bool checked) -{ - refreshIntervalSpinBox->setEnabled(checked); - if (checked) { - refreshIntervalChanged(); - refreshClicked(); - } else { - autoRefreshTimer->stop(); - } -} - -void TabDeveloper::refreshIntervalChanged() -{ - if (autoRefreshCheckBox->isChecked()) { - autoRefreshTimer->start(refreshIntervalSpinBox->value() * 1000); - } -} - -void TabDeveloper::serverStatsResponse(const Response &resp) -{ - requestPending = false; - if (resp.response_code() != Response::RespOk) { - statusLabel->setText(tr("No server statistics available yet.")); - return; - } - - const Response_GetServerStats &response = resp.GetExtension(Response_GetServerStats::ext); - - statsTable->setRowCount(0); - - // Overview section - appendStatRow(tr("Registered users online"), QString::number(response.users_count())); - appendStatRow(tr("Moderators online"), QString::number(response.mods_count())); - appendStatRow(tr("Games running"), QString::number(response.games_count())); - appendStatRow(tr("Traffic sent (last tick)"), formatBytes(response.tx_bytes())); - appendStatRow(tr("Traffic received (last tick)"), formatBytes(response.rx_bytes())); - - const qint64 uptime = static_cast(response.uptime_secs()); - const int days = static_cast(uptime / 86400); - const int hours = static_cast((uptime % 86400) / 3600); - const int minutes = static_cast((uptime % 3600) / 60); - appendStatRow(tr("Server uptime"), days > 0 ? tr("%1d %2h %3m").arg(days).arg(hours).arg(minutes) - : tr("%1h %2m").arg(hours).arg(minutes)); - - const QDateTime snapshotTime = QDateTime::fromSecsSinceEpoch(static_cast(response.timest())); - appendStatRow(tr("Snapshot taken"), snapshotTime.toLocalTime().toString("yyyy-MM-dd HH:mm")); - - // Live metrics section - appendSeparatorRow(tr("Live Metrics")); - appendStatRow(tr("Cards in live games"), QString::number(response.cards_in_games())); - appendStatRow(tr("Total commands processed"), QString::number(response.total_commands())); - - if (response.total_commands() > 0) { - const double avgMs = static_cast(response.total_command_time_ms()) / response.total_commands(); - appendStatRow(tr("Avg command time"), QString::number(avgMs, 'f', 2) + " ms"); - } - appendStatRow(tr("Active command types"), QString::number(response.active_command_types())); - - appendStatRow(tr("Event loop stalls"), QString::number(response.eventloop_stalls_total())); - appendStatRow(tr("Last stall overshoot"), formatDurationMs(response.eventloop_last_stall_ms())); - appendStatRow(tr("Worst stall overshoot"), formatDurationMs(response.eventloop_max_stall_ms())); - - if (response.game_start_count() > 0) { - appendStatRow(tr("Game starts"), QString::number(response.game_start_count())); - const double avgStartMs = static_cast(response.game_start_total_ms()) / response.game_start_count(); - appendStatRow(tr("Avg game start time"), QString::number(avgStartMs, 'f', 1) + " ms"); - } - - // Per-command breakdown table - QList sortedStats(response.command_stats().begin(), response.command_stats().end()); - std::sort(sortedStats.begin(), sortedStats.end(), - [](const auto &a, const auto &b) { return a.total_ms() > b.total_ms(); }); - - commandTable->setRowCount(0); - for (const auto &cs : sortedStats) { - const int row = commandTable->rowCount(); - commandTable->insertRow(row); - commandTable->setItem(row, 0, new QTableWidgetItem(QString::fromStdString(cs.command_name()))); - - auto *countItem = new QTableWidgetItem(QString::number(cs.count())); - countItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); - commandTable->setItem(row, 1, countItem); - - auto *totalItem = new QTableWidgetItem(QString::number(cs.total_ms())); - totalItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); - commandTable->setItem(row, 2, totalItem); - - const double avg = cs.count() > 0 ? static_cast(cs.total_ms()) / cs.count() : 0.0; - auto *avgItem = new QTableWidgetItem(QString::number(avg, 'f', 2)); - avgItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); - commandTable->setItem(row, 3, avgItem); - } - commandTable->resizeColumnsToContents(); - statsTable->resizeColumnsToContents(); - commandTable->resizeColumnsToContents(); - - statusLabel->setText(tr("Updated %1").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm"))); -} diff --git a/cockatrice/src/interface/widgets/tabs/tab_developer.h b/cockatrice/src/interface/widgets/tabs/tab_developer.h deleted file mode 100644 index 727a56bcb..000000000 --- a/cockatrice/src/interface/widgets/tabs/tab_developer.h +++ /dev/null @@ -1,55 +0,0 @@ -/** - * @file tab_developer.h - * @ingroup ServerTabs - */ -//! \todo Document this file. - -#ifndef TAB_DEVELOPER_H -#define TAB_DEVELOPER_H - -#include "tab.h" - -class AbstractClient; -class QCheckBox; -class QLabel; -class QPushButton; -class QSpinBox; -class QTableWidget; -class QTimer; -class Response; - -class TabDeveloper : public Tab -{ - Q_OBJECT -private: - AbstractClient *client; - QTableWidget *statsTable; - QTableWidget *commandTable; - QPushButton *refreshButton; - QLabel *statusLabel; - QCheckBox *autoRefreshCheckBox; - QSpinBox *refreshIntervalSpinBox; - QTimer *autoRefreshTimer; - bool requestPending = false; - - void appendStatRow(const QString &name, const QString &value); - void appendSeparatorRow(const QString §ionTitle); - static QString formatBytes(quint64 bytes); - static QString formatDurationMs(qint64 ms); - -private slots: - void refreshClicked(); - void serverStatsResponse(const Response &resp); - void autoRefreshToggled(bool checked); - void refreshIntervalChanged(); - -public: - explicit TabDeveloper(TabSupervisor *_tabSupervisor, AbstractClient *_client); - void retranslateUi() override; - [[nodiscard]] QString getTabText() const override - { - return tr("Developer"); - } -}; - -#endif diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.cpp b/cockatrice/src/interface/widgets/tabs/tab_game.cpp index 035ab1004..196ea4526 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_game.cpp @@ -266,10 +266,6 @@ void TabGame::resetChatAndPhase() // reset phase markers game->getGameState()->setCurrentPhase(-1); - - // reset spectator state so the replay can rebuild it from the start - game->getPlayerManager()->clearSpectators(); - playerListWidget->clearSpectators(); } void TabGame::emitUserEvent() diff --git a/cockatrice/src/interface/widgets/tabs/tab_logs.cpp b/cockatrice/src/interface/widgets/tabs/tab_logs.cpp index f73d06b57..e3678a903 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_logs.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_logs.cpp @@ -19,8 +19,7 @@ #include #include -TabLog::TabLog(TabSupervisor *_tabSupervisor, AbstractClient *_client, bool _canUseDeveloperCommands) - : Tab(_tabSupervisor), client(_client), canUseDeveloperCommands(_canUseDeveloperCommands) +TabLog::TabLog(TabSupervisor *_tabSupervisor, AbstractClient *_client) : Tab(_tabSupervisor), client(_client) { roomTable = new QTableWidget(); roomTable->setColumnCount(6); @@ -81,9 +80,7 @@ void TabLog::getClicked() if (!mainRoom->isChecked() && !gameRoom->isChecked() && !privateChat->isChecked()) { mainRoom->setChecked(true); gameRoom->setChecked(true); - if (!canUseDeveloperCommands) { - privateChat->setChecked(true); - } + privateChat->setChecked(true); } if (maximumResults->value() == 0) { @@ -120,15 +117,7 @@ void TabLog::getClicked() }; cmd.set_date_range(dateRange); cmd.set_maximum_results(maximumResults->value()); - - PendingCommand *pend; - if (canUseDeveloperCommands) { - // Developers query logs through the developer command family. - pend = client->prepareDeveloperCommand(cmd); - } else { - pend = client->prepareModeratorCommand(cmd); - } - + PendingCommand *pend = client->prepareModeratorCommand(cmd); connect(pend, &PendingCommand::finished, this, &TabLog::viewLogHistory_processResponse); client->sendCommand(pend); } @@ -182,14 +171,6 @@ void TabLog::createDock() mainRoom = new QCheckBox(tr("Main Room")); gameRoom = new QCheckBox(tr("Game Room")); privateChat = new QCheckBox(tr("Private Chat")); - if (canUseDeveloperCommands) { - // Developers cannot query private conversations. - privateChat->setVisible(false); - // The developer family ignores the IP filter server-side, so showing - // the field would silently unfilter the result by it. Hide it. - labelFindIPAddress->setVisible(false); - findIPAddress->setVisible(false); - } pastDays = new QRadioButton(tr("Past X Days: ")); today = new QRadioButton(tr("Today")); diff --git a/cockatrice/src/interface/widgets/tabs/tab_logs.h b/cockatrice/src/interface/widgets/tabs/tab_logs.h index 8e914ea64..5d164dc92 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_logs.h +++ b/cockatrice/src/interface/widgets/tabs/tab_logs.h @@ -33,7 +33,6 @@ class TabLog : public Tab Q_OBJECT private: AbstractClient *client; - bool canUseDeveloperCommands; QLabel *labelFindUserName, *labelFindIPAddress, *labelFindGameName, *labelFindGameID, *labelMessage, *labelMaximum, *labelDescription; LineEditUnfocusable *findUsername, *findIPAddress, *findGameName, *findGameID, *findMessage; @@ -59,7 +58,7 @@ private slots: void restartLayout(); public: - TabLog(TabSupervisor *_tabSupervisor, AbstractClient *_client, bool _canUseDeveloperCommands = false); + TabLog(TabSupervisor *_tabSupervisor, AbstractClient *_client); ~TabLog() override; void retranslateUi() override; [[nodiscard]] QString getTabText() const override diff --git a/cockatrice/src/interface/widgets/tabs/tab_message.cpp b/cockatrice/src/interface/widgets/tabs/tab_message.cpp index 418843178..9506d96f3 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_message.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_message.cpp @@ -98,12 +98,6 @@ void TabMessage::closeEvent(QCloseEvent *event) void TabMessage::sendPrivateMessage(const QString &text) { - if (tabSupervisor->getUserListManager()->isUserIgnored(getUserName())) { - chatView->appendMessage(tr("You have ignored %1; your messages are not delivered.") - .arg(QString::fromStdString(otherUserInfo->name()))); - return; - } - Command_Message cmd; cmd.set_user_name(otherUserInfo->name()); cmd.set_message(text.toStdString()); diff --git a/cockatrice/src/interface/widgets/tabs/tab_moderation.cpp b/cockatrice/src/interface/widgets/tabs/tab_moderation.cpp index b9c37f1af..077b876d2 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_moderation.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_moderation.cpp @@ -381,9 +381,6 @@ void TabModeration::moderatorLoginsResponse(const Response &response) if (login.user_level() & ServerInfo_User::IsAdmin) { levels << tr("Admin"); } - if (login.user_level() & ServerInfo_User::IsDeveloper) { - levels << tr("Developer"); - } if (login.user_level() & ServerInfo_User::IsModerator) { levels << tr("Moderator"); } diff --git a/cockatrice/src/interface/widgets/tabs/tab_replays.cpp b/cockatrice/src/interface/widgets/tabs/tab_replays.cpp index ac4b2cbe9..5618604df 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_replays.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_replays.cpp @@ -1,7 +1,6 @@ #include "tab_replays.h" #include "../../../client/settings/cache_settings.h" -#include "../../pixel_map_generator.h" #include "../interface/widgets/server/remote/remote_replay_list_tree_widget.h" #include "tab_game.h" @@ -103,17 +102,17 @@ QGroupBox *TabReplays::createLeftLayout() // Left side actions aOpenLocalReplay = new QAction(this); - aOpenLocalReplay->setIcon(themePixmap(QStringLiteral("icons/view"))); + aOpenLocalReplay->setIcon(QPixmap("theme:icons/view")); connect(aOpenLocalReplay, &QAction::triggered, this, &TabReplays::actOpenLocalReplay); connect(localDirView, &QTreeView::doubleClicked, this, &TabReplays::actOpenLocalReplay); aRenameLocal = new QAction(this); - aRenameLocal->setIcon(themePixmap(QStringLiteral("icons/rename"))); + aRenameLocal->setIcon(QPixmap("theme:icons/rename")); connect(aRenameLocal, &QAction::triggered, this, &TabReplays::actRenameLocal); aNewLocalFolder = new QAction(this); aNewLocalFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_FileDialogNewFolder)); connect(aNewLocalFolder, &QAction::triggered, this, &TabReplays::actNewLocalFolder); aDeleteLocalReplay = new QAction(this); - aDeleteLocalReplay->setIcon(themePixmap(QStringLiteral("icons/remove_row"))); + aDeleteLocalReplay->setIcon(QPixmap("theme:icons/remove_row")); connect(aDeleteLocalReplay, &QAction::triggered, this, &TabReplays::actDeleteLocalReplay); aOpenReplaysFolder = new QAction(this); @@ -165,24 +164,24 @@ QGroupBox *TabReplays::createRightLayout() // Right side actions aOpenRemoteReplay = new QAction(this); - aOpenRemoteReplay->setIcon(themePixmap(QStringLiteral("icons/view"))); + aOpenRemoteReplay->setIcon(QPixmap("theme:icons/view")); connect(aOpenRemoteReplay, &QAction::triggered, this, &TabReplays::actOpenRemoteReplay); connect(serverDirView, &QTreeView::doubleClicked, this, &TabReplays::actOpenRemoteReplay); aDownload = new QAction(this); - aDownload->setIcon(themePixmap(QStringLiteral("icons/arrow_left_green"))); + aDownload->setIcon(QPixmap("theme:icons/arrow_left_green")); connect(aDownload, &QAction::triggered, this, &TabReplays::actDownload); aKeep = new QAction(this); - aKeep->setIcon(themePixmap(QStringLiteral("icons/lock"))); + aKeep->setIcon(QPixmap("theme:icons/lock")); connect(aKeep, &QAction::triggered, this, &TabReplays::actKeepRemoteReplay); aDeleteRemoteReplay = new QAction(this); - aDeleteRemoteReplay->setIcon(themePixmap(QStringLiteral("icons/remove_row"))); + aDeleteRemoteReplay->setIcon(QPixmap("theme:icons/remove_row")); connect(aDeleteRemoteReplay, &QAction::triggered, this, &TabReplays::actDeleteRemoteReplay); aGetReplayCode = new QAction(this); - aGetReplayCode->setIcon(themePixmap(QStringLiteral("icons/share"))); + aGetReplayCode->setIcon(QPixmap("theme:icons/share")); connect(aGetReplayCode, &QAction::triggered, this, &TabReplays::actGetReplayCode); aSubmitReplayCode = new QAction(this); - aSubmitReplayCode->setIcon(themePixmap(QStringLiteral("icons/search"))); + aSubmitReplayCode->setIcon(QPixmap("theme:icons/search")); connect(aSubmitReplayCode, &QAction::triggered, this, &TabReplays::actSubmitReplayCode); // Add actions to toolbars diff --git a/cockatrice/src/interface/widgets/tabs/tab_room.cpp b/cockatrice/src/interface/widgets/tabs/tab_room.cpp index 866324367..6245b5301 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_room.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_room.cpp @@ -2,7 +2,6 @@ #include "../../../client/settings/cache_settings.h" #include "../../../client/settings/shortcuts_settings.h" -#include "../../pixel_map_generator.h" #include "../interface/widgets/dialogs/dlg_settings.h" #include "../interface/widgets/server/chat_view/chat_view.h" #include "../interface/widgets/server/game_link.h" @@ -99,7 +98,7 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor, connect(aOpenChatSettings, &QAction::triggered, this, &TabRoom::actOpenChatSettings); auto *chatSettingsButton = new QToolButton; - chatSettingsButton->setIcon(themePixmap(QStringLiteral("icons/settings"))); + chatSettingsButton->setIcon(QPixmap("theme:icons/settings")); chatSettingsButton->setMenu(chatSettingsMenu); chatSettingsButton->setPopupMode(QToolButton::InstantPopup); diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp index 462aa420b..ed0ddaf06 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp @@ -15,7 +15,6 @@ #include "tab_card_art_rules.h" #include "tab_deck_editor.h" #include "tab_deck_storage.h" -#include "tab_developer.h" #include "tab_game.h" #include "tab_home.h" #include "tab_logs.h" @@ -120,7 +119,7 @@ TabSupervisor::TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget * : QTabWidget(parent), userInfo(nullptr), client(_client), tabsMenu(tabsMenu), tabHome(nullptr), tabVisualDeckStorage(nullptr), tabServer(nullptr), tabAccount(nullptr), tabDeckStorage(nullptr), tabReplays(nullptr), tabAdmin(nullptr), tabCardArtRules(nullptr), tabLog(nullptr), tabReport(nullptr), - tabModeration(nullptr), tabDeveloper(nullptr), isLocalGame(false) + tabModeration(nullptr), isLocalGame(false) { setElideMode(Qt::ElideRight); setMovable(true); @@ -206,10 +205,6 @@ TabSupervisor::TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget * aTabModeration->setCheckable(true); connect(aTabModeration, &QAction::triggered, this, &TabSupervisor::actTabModeration); - aTabDeveloper = new QAction(this); - aTabDeveloper->setCheckable(true); - connect(aTabDeveloper, &QAction::triggered, this, &TabSupervisor::actTabDeveloper); - connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this, &TabSupervisor::refreshShortcuts); refreshShortcuts(); @@ -252,7 +247,6 @@ void TabSupervisor::retranslateUi() aTabReport->setText(tr("Report Queue")); aTabModeration->setText(tr("Moderation")); aTabCardArtRules->setText(tr("Card Art Rules")); - aTabDeveloper->setText(tr("Developer")); // tabs QList tabs; @@ -265,7 +259,6 @@ void TabSupervisor::retranslateUi() tabs.append(tabReport); tabs.append(tabModeration); tabs.append(tabCardArtRules); - tabs.append(tabDeveloper); QMapIterator roomIterator(roomTabs); while (roomIterator.hasNext()) { tabs.append(roomIterator.next().value()); @@ -535,19 +528,6 @@ void TabSupervisor::start(const ServerInfo_User &_userInfo) } } - if (userInfo->user_level() & ServerInfo_User::IsDeveloper) { - tabsMenu->addSeparator(); - tabsMenu->addAction(aTabDeveloper); - // Developers without moderation rights get log access through their - // own role. Moderators already have the Logs entry from above. - if (!(userInfo->user_level() & ServerInfo_User::IsModerator)) { - tabsMenu->addAction(aTabLog); - if (SettingsCache::instance().tabs().getTabLogOpen()) { - openTabLog(); - } - } - } - retranslateUi(); } @@ -560,7 +540,6 @@ void TabSupervisor::startLocal(const QList &_clients) tabLog = nullptr; tabReport = nullptr; tabModeration = nullptr; - tabDeveloper = nullptr; isLocalGame = true; userInfo = new ServerInfo_User; localClients = _clients; @@ -611,9 +590,6 @@ void TabSupervisor::stop() if (tabCardArtRules) { tabCardArtRules->close(); } - if (tabDeveloper) { - tabDeveloper->close(); - } } QList tabsToDelete; @@ -843,13 +819,7 @@ void TabSupervisor::actTabLog(bool checked) void TabSupervisor::openTabLog() { - // Developers query logs through the developer command family, so tell the - // tab which family to use. The moderator family is strictly stronger, so a - // moderator who also holds the developer bit keeps the moderator path — the - // developer bit only selects the (narrowed) developer family on its own. - const bool useDeveloperCommands = (userInfo->user_level() & ServerInfo_User::IsDeveloper) && - !(userInfo->user_level() & ServerInfo_User::IsModerator); - tabLog = new TabLog(this, client, useDeveloperCommands); + tabLog = new TabLog(this, client); myAddTab(tabLog, aTabLog); connect(tabLog, &QObject::destroyed, this, [this] { tabLog = nullptr; @@ -911,27 +881,6 @@ void TabSupervisor::openTabModeration(const QString &userName) aTabModeration->setChecked(true); } -void TabSupervisor::actTabDeveloper(bool checked) -{ - if (checked && !tabDeveloper) { - openTabDeveloper(); - setCurrentWidget(tabDeveloper); - } else if (!checked && tabDeveloper) { - tabDeveloper->closeRequest(); - } -} - -void TabSupervisor::openTabDeveloper() -{ - tabDeveloper = new TabDeveloper(this, client); - myAddTab(tabDeveloper, aTabDeveloper); - connect(tabDeveloper, &QObject::destroyed, this, [this] { - tabDeveloper = nullptr; - aTabDeveloper->setChecked(false); - }); - aTabDeveloper->setChecked(true); -} - void TabSupervisor::updatePingTime(int value, int max) { if (!tabServer) { @@ -1114,13 +1063,6 @@ TabMessage *TabSupervisor::addMessageTab(const QString &receiverName, bool focus return tab; } - if (focus && userListManager->isUserIgnored(receiverName)) { - QMessageBox::information( - this, tr("Ignored user"), - tr("You have ignored %1. Remove them from your ignore list to open a private chat.").arg(receiverName)); - return nullptr; - } - tab = new TabMessage(this, client, *userInfo, otherUser, userOnline); connect(tab, &TabMessage::talkClosing, this, &TabSupervisor::talkLeft); connect(tab, &TabMessage::maximizeClient, this, &TabSupervisor::maximizeMainWindow); @@ -1300,7 +1242,7 @@ void TabSupervisor::tabUserEvent(bool globalEvent) auto *tab = static_cast(sender()); if (tab != currentWidget()) { tab->setContentsChanged(true); - setTabIcon(indexOf(tab), themePixmap(QStringLiteral("icons/tab_changed"))); + setTabIcon(indexOf(tab), QPixmap("theme:icons/tab_changed")); } if (globalEvent && SettingsCache::instance().userInterface().getNotificationsEnabled()) { QApplication::alert(this); @@ -1335,21 +1277,7 @@ void TabSupervisor::processGameEventContainer(const GameEventContainer &cont) void TabSupervisor::processUserMessageEvent(const Event_UserMessage &event) { - // "Ignore all private messages" silences every PM, including messages to - // already-open tabs — unlike the unregistered/non-buddy filters below, - // which only apply when creating a new tab. Messages from moderators/admins - // are exempt to ensure warnings still reach users. QString senderName = QString::fromStdString(event.sender_name()); - if (SettingsCache::instance().chat().getIgnoreAllPrivateMessages()) { - const ServerInfo_User *onlineUserInfo = userListManager->getOnlineUser(senderName); - if (!onlineUserInfo) { - return; - } - const UserLevelFlags userLevel(onlineUserInfo->user_level()); - if (!userLevel.testFlag(ServerInfo_User::IsModerator) && !userLevel.testFlag(ServerInfo_User::IsAdmin)) { - return; - } - } TabMessage *tab = messageTabs.value(senderName); if (!tab) { tab = messageTabs.value(QString::fromStdString(event.receiver_name())); diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.h b/cockatrice/src/interface/widgets/tabs/tab_supervisor.h index aec1d7418..b389bad3e 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.h +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.h @@ -45,7 +45,6 @@ class TabReport; class TabModeration; class TabAccount; class TabDeckEditor; -class TabDeveloper; class TabLog; class RoomEvent; class GameEventContainer; @@ -109,7 +108,6 @@ private: TabLog *tabLog; TabReport *tabReport; TabModeration *tabModeration; - TabDeveloper *tabDeveloper; QMap roomTabs; QMap gameTabs; QList replayTabs; @@ -119,7 +117,7 @@ private: QAction *aTabHome, *aTabDeckEditor, *aTabVisualDeckEditor, *aTabEdhRec, *aTabArchidekt, *aTabVisualDeckStorage, *aTabVisualDatabaseDisplay, *aTabServer, *aTabAccount, *aTabDeckStorage, *aTabReplays, *aTabAdmin, - *aTabCardArtRules, *aTabLog, *aTabReport, *aTabModeration, *aTabDeveloper; + *aTabCardArtRules, *aTabLog, *aTabReport, *aTabModeration; int myAddTab(Tab *tab, QAction *manager = nullptr); void addCloseButtonToTab(Tab *tab, int tabIndex, QAction *manager); @@ -209,7 +207,6 @@ private slots: void actTabLog(bool checked); void actTabReport(bool checked); void actTabModeration(bool checked); - void actTabDeveloper(bool checked); void openTabVisualDeckStorage(); void openTabHome(); @@ -221,7 +218,6 @@ private slots: void openTabCardArtRules(); void openTabLog(); void openTabReport(); - void openTabDeveloper(); void updateCurrent(int index); void updatePingTime(int value, int max); diff --git a/cockatrice/src/interface/widgets/utility/sequence_edit.cpp b/cockatrice/src/interface/widgets/utility/sequence_edit.cpp index 06561a41d..c6bf289ba 100644 --- a/cockatrice/src/interface/widgets/utility/sequence_edit.cpp +++ b/cockatrice/src/interface/widgets/utility/sequence_edit.cpp @@ -2,7 +2,6 @@ #include "../../../client/settings/cache_settings.h" #include "../../../client/settings/shortcuts_settings.h" -#include "../../pixel_map_generator.h" #include #include @@ -15,8 +14,8 @@ SequenceEdit::SequenceEdit(const QString &_shortcutName, QWidget *parent) : QWid defaultButton = new QPushButton("", this); lineEdit->setMinimumWidth(70); - clearButton->setIcon(themePixmap(QStringLiteral("icons/clearsearch"))); - defaultButton->setIcon(themePixmap(QStringLiteral("icons/update"))); + clearButton->setIcon(QPixmap("theme:icons/clearsearch")); + defaultButton->setIcon(QPixmap("theme:icons/update")); auto *layout = new QHBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp index a59a068dd..4a558a5e0 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp @@ -1,6 +1,5 @@ #include "visual_database_display_filter_toolbar_widget.h" -#include "../../pixel_map_generator.h" #include "../deck_editor/card_database_view.h" #include "visual_database_display_widget.h" @@ -61,22 +60,22 @@ VisualDatabaseDisplayFilterToolbarWidget::VisualDatabaseDisplayFilterToolbarWidg }); quickFilterSaveLoadWidget = new SettingsButtonWidget(this); - quickFilterSaveLoadWidget->setButtonIcon(themePixmap(QStringLiteral("icons/floppy_disk"))); + quickFilterSaveLoadWidget->setButtonIcon(QPixmap("theme:icons/floppy_disk")); quickFilterNameWidget = new SettingsButtonWidget(this); - quickFilterNameWidget->setButtonIcon(themePixmap(QStringLiteral("icons/pen_to_square"))); + quickFilterNameWidget->setButtonIcon(QPixmap("theme:icons/pen_to_square")); quickFilterMainTypeWidget = new SettingsButtonWidget(this); - quickFilterMainTypeWidget->setButtonIcon(themePixmap(QStringLiteral("icons/circle_half_stroke"))); + quickFilterMainTypeWidget->setButtonIcon(QPixmap("theme:icons/circle_half_stroke")); quickFilterSubTypeWidget = new SettingsButtonWidget(this); - quickFilterSubTypeWidget->setButtonIcon(themePixmap(QStringLiteral("icons/dragon"))); + quickFilterSubTypeWidget->setButtonIcon(QPixmap("theme:icons/dragon")); quickFilterSetWidget = new SettingsButtonWidget(this); - quickFilterSetWidget->setButtonIcon(themePixmap(QStringLiteral("icons/scroll"))); + quickFilterSetWidget->setButtonIcon(QPixmap("theme:icons/scroll")); quickFilterFormatLegalityWidget = new SettingsButtonWidget(this); - quickFilterFormatLegalityWidget->setButtonIcon(themePixmap(QStringLiteral("icons/scale_balanced"))); + quickFilterFormatLegalityWidget->setButtonIcon(QPixmap("theme:icons/scale_balanced")); retranslateUi(); } diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp index f4530a12b..76bbf344b 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp @@ -66,7 +66,7 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, searchEdit->setPlaceholderText(tr("Search by card name (or search expressions)")); searchEdit->setClearButtonEnabled(true); searchEdit->addAction(loadColorAdjustedPixmap("theme:icons/search"), QLineEdit::LeadingPosition); - auto help = searchEdit->addAction(themePixmap(QStringLiteral("icons/info")), QLineEdit::TrailingPosition); + auto help = searchEdit->addAction(QPixmap("theme:icons/info"), QLineEdit::TrailingPosition); connect(help, &QAction::triggered, this, [this] { createSearchSyntaxHelpWindow(searchEdit); }); setFocusProxy(searchEdit); @@ -121,7 +121,7 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, clearFilterWidget = new QToolButton(); clearFilterWidget->setFixedSize(32, 32); - clearFilterWidget->setIcon(themePixmap(QStringLiteral("icons/delete"))); + clearFilterWidget->setIcon(QPixmap("theme:icons/delete")); connect(clearFilterWidget, &QToolButton::clicked, this, [this] { filterModel->blockSignals(true); filterModel->filterTree()->blockSignals(true); diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp index 28abe480e..f44c9c3ef 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp @@ -1,6 +1,5 @@ #include "visual_deck_display_options_widget.h" -#include "../../pixel_map_generator.h" #include "../tabs/visual_deck_editor/tab_deck_editor_visual.h" #include @@ -48,7 +47,7 @@ VisualDeckDisplayOptionsWidget::VisualDeckDisplayOptionsWidget(QWidget *parent) sortByLabel = new QLabel(this); sortCriteriaButton = new SettingsButtonWidget(this); - sortCriteriaButton->setButtonIcon(themePixmap(QStringLiteral("icons/sort_arrow_down"))); + sortCriteriaButton->setButtonIcon(QPixmap("theme:icons/sort_arrow_down")); sortLabel = new QLabel(sortCriteriaButton); sortLabel->setWordWrap(true); @@ -93,7 +92,7 @@ void VisualDeckDisplayOptionsWidget::retranslateUi() sortLabel->setText(tr("Click and drag to change the sort order within the groups")); sortCriteriaButton->setToolTip(tr("Configure how cards are sorted within their groups")); displayTypeButton->setButtonText(tr("Toggle Layout: Overlap")); - displayTypeButton->setButtonIcon(themePixmap(QStringLiteral("icons/scales"))); + displayTypeButton->setButtonIcon(QPixmap("theme:icons/scales")); displayTypeButton->setToolTip( tr("Change how cards are displayed within zones (i.e. overlapped or fully visible.)")); } @@ -118,11 +117,11 @@ void VisualDeckDisplayOptionsWidget::updateDisplayType() switch (currentDisplayType) { case DisplayType::Flat: displayTypeButton->setButtonText(tr("Toggle Layout: Flat")); - displayTypeButton->setButtonIcon(themePixmap(QStringLiteral("icons/scroll"))); + displayTypeButton->setButtonIcon(QPixmap("theme:icons/scroll")); break; case DisplayType::Overlap: displayTypeButton->setButtonText(tr("Toggle Layout: Overlap")); - displayTypeButton->setButtonIcon(themePixmap(QStringLiteral("icons/scales"))); + displayTypeButton->setButtonIcon(QPixmap("theme:icons/scales")); break; } emit displayTypeChanged(currentDisplayType); diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp index 064fbed5d..e3261b346 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp @@ -4,7 +4,6 @@ #include "../../../main.h" #include "../../deck_loader/deck_loader.h" #include "../../layouts/overlap_layout.h" -#include "../../pixel_map_generator.h" #include "../cards/card_info_picture_with_text_overlay_widget.h" #include "../cards/deck_card_zone_display_widget.h" #include "../general/layout_containers/flow_widget.h" @@ -132,7 +131,7 @@ void VisualDeckEditorWidget::initializeSearchBarAndCompleter() // Search button functionality searchPushButton = new CompactPushButton(searchContainer); - searchPushButton->setButtonIcon(themePixmap(QStringLiteral("icons/search"))); + searchPushButton->setButtonIcon(QPixmap("theme:icons/search")); connect(searchPushButton, &QPushButton::clicked, this, [=, this]() { ExactCard card = CardDatabaseManager::query()->getCard({searchBar->text()}); if (card) { diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp index 406527893..baa5e5792 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp @@ -25,7 +25,7 @@ VisualDeckStorageSearchWidget::VisualDeckStorageSearchWidget(QWidget *parent) : searchBar->setClearButtonEnabled(true); searchBar->addAction(loadColorAdjustedPixmap("theme:icons/search"), QLineEdit::LeadingPosition); - auto help = searchBar->addAction(themePixmap(QStringLiteral("icons/info")), QLineEdit::TrailingPosition); + auto help = searchBar->addAction(QPixmap("theme:icons/info"), QLineEdit::TrailingPosition); connect(help, &QAction::triggered, this, [this] { createDeckSearchSyntaxHelpWindow(searchBar); }); layout->addWidget(searchBar); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp index da7ddc368..acb0dcab2 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp @@ -1,7 +1,6 @@ #include "visual_deck_storage_widget.h" #include "../../../client/settings/cache_settings.h" -#include "../../pixel_map_generator.h" #include "../quick_settings/settings_button_widget.h" #include "deck_preview/deck_preview_color_identity_filter_widget.h" #include "deck_preview/deck_preview_widget.h" @@ -44,7 +43,7 @@ VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(pare searchWidget = new VisualDeckStorageSearchWidget(this); refreshButton = new QToolButton(this); - refreshButton->setIcon(themePixmap(QStringLiteral("icons/reload"))); + refreshButton->setIcon(QPixmap("theme:icons/reload")); refreshButton->setFixedSize(32, 32); connect(refreshButton, &QPushButton::clicked, this, &VisualDeckStorageWidget::refreshIfPossible); diff --git a/cockatrice/src/interface/window_main.cpp b/cockatrice/src/interface/window_main.cpp index 722df90ef..4567991c8 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" @@ -45,7 +44,6 @@ #include "intents/intent_open_server_room_by_name.h" #include "intents/url_parser.h" #include "logger.h" -#include "pixel_map_generator.h" #include "version_string.h" #include "widgets/dialogs/dlg_connect.h" #include "widgets/server/handle_public_servers.h" @@ -93,8 +91,8 @@ #include #define GITHUB_PAGES_URL "https://cockatrice.github.io" -#define GITHUB_CONTRIBUTORS_URL "https://github.com/Cockatrice/Cockatrice/graphs/contributors" -#define GITHUB_CONTRIBUTE_URL "https://github.com/Cockatrice/Cockatrice#" +#define GITHUB_CONTRIBUTORS_URL "https://github.com/Cockatrice/Cockatrice/graphs/contributors?type=c" +#define GITHUB_CONTRIBUTE_URL "https://github.com/Cockatrice/Cockatrice#cockatrice" #define GITHUB_TRANSIFEX_TRANSLATORS_URL "https://github.com/Cockatrice/Cockatrice/wiki/Translator-Hall-of-Fame" #define GITHUB_TRANSLATOR_FAQ_URL "https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ" #define GITHUB_ISSUES_URL "https://github.com/Cockatrice/Cockatrice/issues" @@ -247,8 +245,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(); } @@ -275,8 +271,7 @@ void MainWindow::actAbout() GITHUB_TROUBLESHOOTING_URL + "'>" + tr("Troubleshooting") + "
" + "" + tr("F.A.Q.") + "
"), QMessageBox::Ok, this); - mb.setIconPixmap( - themePixmap(QStringLiteral("cockatrice")).scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + mb.setIconPixmap(QPixmap("theme:cockatrice").scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation)); mb.setTextInteractionFlags(Qt::TextBrowserInteraction); mb.exec(); } @@ -328,7 +323,7 @@ void MainWindow::retranslateUi() aRegister->setText(tr("&Register to server...")); aForgotPassword->setText(tr("&Restore password...")); aSettings->setText(tr("&Settings...")); - aSettings->setIcon(themePixmap(QStringLiteral("icons/settings"))); + aSettings->setIcon(QPixmap("theme:icons/settings")); aExit->setText(tr("&Exit")); #if defined(__APPLE__) /* For OSX */ @@ -687,7 +682,6 @@ void MainWindow::runFirstRunWizard() connect(wizard, &FirstRunWizard::cardDatabaseUpdateRequested, this, &MainWindow::actCheckCardUpdatesBackground); connect(wizard, &FirstRunWizard::manualCardDatabaseSetupRequested, this, &MainWindow::actCheckCardUpdates); connect(this, &MainWindow::cardDatabaseUpdateFinished, wizard, &FirstRunWizard::onCardDatabaseUpdateFinished); - connect(this, &MainWindow::cardDatabaseUpdateProgress, wizard, &FirstRunWizard::onCardDatabaseUpdateProgress); connect(wizard, &FirstRunWizard::registerRequested, connectionController, &ConnectionController::registerToServer); connect(wizard, &FirstRunWizard::connectRequested, connectionController, &ConnectionController::connectToServer); @@ -822,7 +816,7 @@ void MainWindow::createTrayIcon() trayIcon = new QSystemTrayIcon(this); trayIcon->setContextMenu(trayIconMenu); - trayIcon->setIcon(themePixmap(QStringLiteral("cockatrice"))); + trayIcon->setIcon(QPixmap("theme:cockatrice")); trayIcon->show(); } @@ -849,17 +843,6 @@ void MainWindow::closeEvent(QCloseEvent *event) } bClosingDown = true; - if (cardUpdateProcess && cardUpdateProcess->state() != QProcess::NotRunning) { - if (QMessageBox::question(this, tr("Are you sure?"), - tr("A card database update is still running. Quitting now will cancel it.\n" - "Are you sure you want to quit?"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) { - event->ignore(); - bClosingDown = false; - return; - } - } - if (!tabSupervisor->close()) { event->ignore(); bClosingDown = false; @@ -1053,7 +1036,7 @@ void MainWindow::createCardUpdateProcess(bool background) if (dir.exists(binaryName)) { updaterCmd = dir.absoluteFilePath(binaryName); - } else { // try and find the directory Oracle is stored in the build directory + } else { // try and find the directory oracle is stored in the build directory QDir findLocalDir(dir); findLocalDir.cdUp(); findLocalDir.cd(getCardUpdaterBinaryName()); @@ -1074,45 +1057,11 @@ void MainWindow::createCardUpdateProcess(bool background) if (!background) { cardUpdateProcess->start(updaterCmd, QStringList()); } else { - cardUpdateOutputBuffer.clear(); - connect(cardUpdateProcess, &QProcess::readyReadStandardOutput, this, &MainWindow::cardUpdateProgressOutput); cardUpdateProcess->start(updaterCmd, QStringList("-b")); statusBar()->showMessage(tr("Card database update running.")); } } -void MainWindow::cardUpdateProgressOutput() -{ - if (!cardUpdateProcess) { - return; - } - cardUpdateOutputBuffer.append(cardUpdateProcess->readAllStandardOutput()); - while (true) { - const int newline = cardUpdateOutputBuffer.indexOf('\n'); - if (newline < 0) { - break; - } - const QByteArray line = cardUpdateOutputBuffer.left(newline).trimmed(); - cardUpdateOutputBuffer.remove(0, newline + 1); - // Protocol emitted by `oracle -b`: "PROGRESS " - if (!line.startsWith("PROGRESS ")) { - continue; - } - const QList parts = line.split(' '); - if (parts.size() != 4) { - continue; - } - bool doneOk = false; - bool totalOk = false; - const qint64 done = parts.at(2).toLongLong(&doneOk); - const qint64 total = parts.at(3).toLongLong(&totalOk); - if (!doneOk || !totalOk || done < 0 || total < 0) { - continue; - } - emit cardDatabaseUpdateProgress(QString::fromLatin1(parts.at(1)), done, total); - } -} - void MainWindow::exitCardDatabaseUpdate() { if (!cardUpdateProcess) { @@ -1160,8 +1109,6 @@ void MainWindow::cardUpdateError(QProcess::ProcessError err) void MainWindow::cardUpdateFinished(int exitCode, QProcess::ExitStatus exitStatus) { - cardUpdateProgressOutput(); // drain any progress lines not yet parsed - const bool success = (exitStatus == QProcess::NormalExit) && (exitCode == 0); if (exitStatus == QProcess::NormalExit) { SettingsCache::instance().updates().setLastCardUpdateCheck(QDateTime::currentDateTime().date()); diff --git a/cockatrice/src/interface/window_main.h b/cockatrice/src/interface/window_main.h index 08481fd36..920145552 100644 --- a/cockatrice/src/interface/window_main.h +++ b/cockatrice/src/interface/window_main.h @@ -68,11 +68,6 @@ signals: /** @brief Emitted after the background card-database update subprocess exits. */ void cardDatabaseUpdateFinished(bool success); - /** @brief Emitted while the background card-database update subprocess runs. - * @p stage is one of "download", "scan" or "import"; @p done/@p total - * are byte counts for the first two stages and set indices for "import". */ - void cardDatabaseUpdateProgress(const QString &stage, qint64 done, qint64 total); - public slots: void actCheckCardUpdates(); void actCheckCardUpdatesBackground(); @@ -101,7 +96,6 @@ private slots: void cardUpdateError(QProcess::ProcessError err); void cardUpdateFinished(int exitCode, QProcess::ExitStatus exitStatus); - void cardUpdateProgressOutput(); void refreshShortcuts(); void cardDatabaseLoadingFailed(); void cardDatabaseNewSetsFound(int numUnknownSets, QStringList unknownSetsNames); @@ -165,7 +159,6 @@ private: LatencyStatusWidget *latencyStatus = nullptr; ///< status bar widget with live round-trip stats and history graph bool bHasActivated, askedForDbUpdater; QProcess *cardUpdateProcess; - QByteArray cardUpdateOutputBuffer; DlgViewLog *logviewDialog; GameReplay *replay; DlgTipOfTheDay *tip; diff --git a/cockatrice/src/main.cpp b/cockatrice/src/main.cpp index 829e08742..d8aa1cd08 100644 --- a/cockatrice/src/main.cpp +++ b/cockatrice/src/main.cpp @@ -231,7 +231,7 @@ int main(int argc, char *argv[]) // These values are only used by the settings loader/saver // Wrong or outdated values are kept to not break things QCoreApplication::setOrganizationName("Cockatrice"); - QCoreApplication::setOrganizationDomain("cockatrice.github.io"); + QCoreApplication::setOrganizationDomain("cockatrice.de"); QCoreApplication::setApplicationName("Cockatrice"); QCoreApplication::setApplicationVersion(VERSION_STRING); @@ -250,7 +250,7 @@ int main(int argc, char *argv[]) // Command-line parser QCommandLineParser parser; - parser.setApplicationDescription("Cockatrice Client"); + parser.setApplicationDescription("Cockatrice"); parser.addHelpOption(); parser.addVersionOption(); @@ -349,9 +349,9 @@ int main(int argc, char *argv[]) } qCInfo(MainLog) << "MainWindow constructor finished"; - ui.setWindowIcon(themePixmap(QStringLiteral("cockatrice"))); - // Set name of the app desktop file; used by wayland to load the window icon - QGuiApplication::setDesktopFileName("Cockatrice"); + ui.setWindowIcon(QPixmap("theme:cockatrice")); + // set name of the app desktop file; used by wayland to load the window icon + QGuiApplication::setDesktopFileName("cockatrice"); SettingsCache::instance().network().setClientID(generateClientID()); 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 0013cb475..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,112 +8,26 @@ #include #include #include +#include #include #include #include -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) +#if QT_VERSION < 0x050600 +// qHash on QRegularExpression was added in 5.6, FIX IT +uint qHash(const QRegularExpression &key, uint seed) noexcept { - bool ok = false; - const double value = valueString.toDouble(&ok); - if (!ok) { - return fallback; - } - return qBound(min, value, max); + return qHash(key.pattern(), seed); // call qHash on pattern QString instead } +#endif + +static const QString CURRENT_SIDEBOARD_PLAN_KEY = ""; 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") { - QString providerId = xml->attributes().value("providerId").toString(); - QString cardName = xml->readElementText(); - bannerCard = {cardName, providerId}; - } else if (childName == "playmatCard") { - QString providerId = xml->attributes().value("providerId").toString(); - // Attributes are read before readElementText consumes the element. - 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(); - 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 = parseClampedParam(marginLStr, 0.07, 0.0, 0.95); - playmat.params.marginPctR = parseClampedParam(marginRStr, 0.07, 0.0, 0.95); - playmat.params.verticalOffset = parseClampedParam(vOffStr, 0.33, 0.0, 1.0); - playmat.params.zoom = parseClampedParam(zoomStr, 1.0, 0.1, 4.0); - } 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() { } @@ -149,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; @@ -166,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); @@ -260,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 229e2077c..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: 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 35c7943b4..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 @@ -197,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 94fd87680..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,7 +144,7 @@ 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); }; 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/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 510a710cd..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 @@ -164,11 +164,12 @@ int InnerDecklistNode::readElement(QXmlStreamReader *xml, int limit) } else if (childName == "card") { int amount = xml->attributes().value("number").toString().toInt(); amount = qMin(amount, limit - totalCards); - 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()); + 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; 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 958229883..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 @@ -229,7 +229,7 @@ public: * @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. 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_interfaces/libcockatrice/interfaces/interface_chat_settings_provider.h b/libcockatrice_interfaces/libcockatrice/interfaces/interface_chat_settings_provider.h index cdf2da5eb..cd9ad29e1 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/interface_chat_settings_provider.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/interface_chat_settings_provider.h @@ -20,7 +20,6 @@ public: [[nodiscard]] virtual bool getShowMessagePopup() const = 0; [[nodiscard]] virtual bool getShowMentionPopup() const = 0; [[nodiscard]] virtual bool getRoomHistory() const = 0; - [[nodiscard]] virtual bool getIgnoreAllPrivateMessages() const = 0; [[nodiscard]] virtual QString getHighlightWords() const = 0; }; 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/client/abstract/abstract_client.cpp b/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.cpp index 687d93666..d6316deb3 100644 --- a/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.cpp +++ b/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.cpp @@ -253,24 +253,3 @@ PendingCommand *AbstractClient::prepareAdminCommand(const ::google::protobuf::Me c->GetReflection()->MutableMessage(c, cmd.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(cmd); return new PendingCommand(cont); } - -PendingCommand *AbstractClient::prepareDeveloperCommand(const ::google::protobuf::Message &cmd) -{ - CommandContainer cont; - DeveloperCommand *c = cont.add_developer_command(); - // A developer command message may also be usable through other command - // families, so select the extension scoped to DeveloperCommand rather than - // guessing by name. - const ::google::protobuf::Descriptor *cmdDescriptor = cmd.GetDescriptor(); - const ::google::protobuf::Descriptor *developerDescriptor = DeveloperCommand::descriptor(); - const ::google::protobuf::FieldDescriptor *developerExtension = nullptr; - for (int i = 0; i < cmdDescriptor->extension_count(); ++i) { - if (cmdDescriptor->extension(i)->containing_type() == developerDescriptor) { - developerExtension = cmdDescriptor->extension(i); - break; - } - } - Q_ASSERT(developerExtension != nullptr); - c->GetReflection()->MutableMessage(c, developerExtension)->CopyFrom(cmd); - return new PendingCommand(cont); -} diff --git a/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.h b/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.h index af22a5c9d..1ef9a31e4 100644 --- a/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.h +++ b/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.h @@ -173,7 +173,6 @@ public: static PendingCommand *prepareRoomCommand(const ::google::protobuf::Message &cmd, int roomId); static PendingCommand *prepareModeratorCommand(const ::google::protobuf::Message &cmd); static PendingCommand *prepareAdminCommand(const ::google::protobuf::Message &cmd); - static PendingCommand *prepareDeveloperCommand(const ::google::protobuf::Message &cmd); QMap clientFeatures; }; diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp index 6b4101a99..957a89792 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp @@ -66,15 +66,6 @@ Server_AbstractPlayer::Server_AbstractPlayer(Server_Game *_game, Server_AbstractPlayer::~Server_AbstractPlayer() = default; -int Server_AbstractPlayer::getCardCount() const -{ - int result = 0; - for (auto *zone : zones) { - result += zone->getCards().size(); - } - return result; -} - void Server_AbstractPlayer::prepareDestroy() { delete deck; diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.h index 4cc79c5fe..85fbc0557 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.h @@ -43,8 +43,6 @@ public: Server_AbstractUserInterface *_handler); ~Server_AbstractPlayer() override; void prepareDestroy() override; - /// Total cards across all of this player's zones. The caller must hold the game's mutex. - int getCardCount() const; const DeckList *getDeckList() const { return deck; 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 799b1e7ee..43209e994 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp @@ -32,7 +32,6 @@ #include "server_spectator.h" #include -#include #include #include #include @@ -239,17 +238,6 @@ int Server_Game::getPlayerCount() const return participants.size() - getSpectatorCount(); } -int Server_Game::getCardsInGame() const -{ - QMutexLocker locker(&gameMutex); - - int result = 0; - for (auto *player : getPlayers()) { - result += player->getCardCount(); - } - return result; -} - int Server_Game::getSpectatorCount() const { QMutexLocker locker(&gameMutex); @@ -342,9 +330,6 @@ void Server_Game::doStartGameIfReady(bool forceStartGame) } } - // Only actual starts are timed. The early returns above are no-ops. - QElapsedTimer startupTimer; - startupTimer.start(); players = getPlayers(); // players could have been kicked, get new list of players if (lifecycleStrategy->onGameStarting(this) == Server_GameLifecycleStrategy::StartAction::Handled) { locker.unlock(); @@ -388,7 +373,6 @@ void Server_Game::doStartGameIfReady(bool forceStartGame) activePlayer = -1; nextTurn(); - room->getServer()->observeGameStartDurationMs(startupTimer.nsecsElapsed() / 1000000); locker.unlock(); diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h index 1ed4fe4ca..1b9f651bd 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h @@ -123,8 +123,6 @@ public: return gameStarted; } int getPlayerCount() const; - /// Total cards across all players' zones. Takes gameMutex itself. - int getCardsInGame() const; int getSpectatorCount() const; QMap getPlayers() const; Server_AbstractPlayer *getPlayer(int id) const; diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server.h b/libcockatrice_network/libcockatrice/network/server/remote/server.h index 3d27f4210..0ded27afa 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/server.h @@ -180,11 +180,6 @@ public: { return false; } - /// Called once per actual game start with how long bringing every player's - /// zones online took, so servers can spot deck sizes that wedge threads. - virtual void observeGameStartDurationMs(qint64 /* elapsedMs */) - { - } Server_DatabaseInterface *getDatabaseInterface() const; int getNextLocalGameId() diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.cpp b/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.cpp index 8422d703d..899df6529 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.cpp @@ -388,33 +388,6 @@ Response::ResponseCode Server_ProtocolHandler::processAdminCommandContainer(cons return finalResponseCode; } -Response::ResponseCode Server_ProtocolHandler::processDeveloperCommandContainer(const CommandContainer &cont, - ResponseContainer &rc) -{ - if (!userInfo) { - return Response::RespLoginNeeded; - } - if (!(userInfo->user_level() & ServerInfo_User::IsDeveloper)) { - return Response::RespLoginNeeded; - } - - resetIdleTimer(); - - Response::ResponseCode finalResponseCode = Response::RespOk; - for (int i = cont.developer_command_size() - 1; i >= 0; --i) { - Response::ResponseCode resp = Response::RespInvalidCommand; - const DeveloperCommand &sc = cont.developer_command(i); - const int num = getPbExtension(sc); - logDebugMessage(getSafeDebugString(sc)); - - resp = processExtendedDeveloperCommand(num, sc, rc); - if (resp != Response::RespOk) { - finalResponseCode = resp; - } - } - return finalResponseCode; -} - void Server_ProtocolHandler::processCommandContainer(const CommandContainer &cont) { // Command processing must be disabled after prepareDestroy() has been called. @@ -437,8 +410,6 @@ void Server_ProtocolHandler::processCommandContainer(const CommandContainer &con finalResponseCode = processModeratorCommandContainer(cont, responseContainer); } else if (cont.admin_command_size()) { finalResponseCode = processAdminCommandContainer(cont, responseContainer); - } else if (cont.developer_command_size()) { - finalResponseCode = processDeveloperCommandContainer(cont, responseContainer); } else { finalResponseCode = Response::RespInvalidCommand; } @@ -483,12 +454,11 @@ void Server_ProtocolHandler::pingClockTimeout() prepareDestroy(); } - // PrivLevel users, Moderators, Admins, and Developers are not subject to the server idle timeout policy + // PrivLevel users, Moderators, and Admins are not subject to the server idle timeout policy const bool hasPrivLevel = userInfo && QString::fromStdString(userInfo->privlevel()).toLower() != "none"; - const bool isStaff = - userInfo && (userInfo->user_level() & - (ServerInfo_User::IsModerator | ServerInfo_User::IsAdmin | ServerInfo_User::IsDeveloper)); - if (!hasPrivLevel && !isStaff) { + const bool isModOrAdmin = + userInfo && (userInfo->user_level() & (ServerInfo_User::IsModerator | ServerInfo_User::IsAdmin)); + if (!hasPrivLevel && !isModOrAdmin) { if ((server->getIdleClientTimeout() > 0) && (idleClientWarningSent)) { if (timeRunning - lastActionReceived > server->getIdleClientTimeout()) { prepareDestroy(); diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.h b/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.h index 2c8efe50e..0d05b91c8 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.h @@ -27,7 +27,6 @@ class CommandContainer; class SessionCommand; class ModeratorCommand; class AdminCommand; -class DeveloperCommand; class Command_Ping; class Command_Login; @@ -99,12 +98,6 @@ private: { return Response::RespFunctionNotAllowed; } - Response::ResponseCode processDeveloperCommandContainer(const CommandContainer &cont, ResponseContainer &rc); - virtual Response::ResponseCode - processExtendedDeveloperCommand(int /* cmdType */, const DeveloperCommand & /* cmd */, ResponseContainer & /* rc */) - { - return Response::RespFunctionNotAllowed; - } void resetIdleTimer(); private slots: @@ -136,7 +129,7 @@ public: return timeRunning - lastDataReceived; } bool addSaidMessageSize(int size); - virtual void processCommandContainer(const CommandContainer &cont); + void processCommandContainer(const CommandContainer &cont); void sendProtocolItem(const Response &item); void sendProtocolItem(const SessionEvent &item); diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt b/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt index bc4814d5e..9df7291a8 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt @@ -24,8 +24,6 @@ set(PROTO_FILES command_deck_share_create.proto command_deck_share_download.proto command_deck_share_list.proto - command_deck_share_list_mine.proto - command_deck_share_remove.proto command_deck_upload.proto command_del_counter.proto command_delete_arrow.proto @@ -33,7 +31,6 @@ set(PROTO_FILES command_dump_zone.proto command_flip_card.proto command_game_say.proto - command_get_server_stats.proto command_inc_card_counter.proto command_inc_counter.proto command_kick_from_game.proto @@ -80,7 +77,6 @@ set(PROTO_FILES context_ready_start.proto context_set_sideboard_lock.proto context_undo_draw.proto - developer_commands.proto event_add_to_list.proto event_attach_card.proto event_change_zone_properties.proto @@ -148,13 +144,11 @@ set(PROTO_FILES response_deck_share_create.proto response_deck_share_download.proto response_deck_share_list.proto - response_deck_share_list_mine.proto response_deck_upload.proto response_dump_zone.proto response_forgotpasswordrequest.proto response_get_admin_notes.proto response_get_games_of_user.proto - response_get_server_stats.proto response_get_user_info.proto response_join_room.proto response_list_users.proto @@ -188,7 +182,6 @@ set(PROTO_FILES serverinfo_chat_message.proto serverinfo_counter.proto serverinfo_deck_share_item.proto - serverinfo_deck_share_summary.proto serverinfo_deckstorage.proto serverinfo_game.proto serverinfo_gametype.proto diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/admin_commands.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/admin_commands.proto index f1f85e376..f8b34b3f8 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/admin_commands.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/admin_commands.proto @@ -37,7 +37,6 @@ message Command_AdjustMod { required string user_name = 1; optional bool should_be_mod = 2; optional bool should_be_judge = 3; - optional bool should_be_developer = 4; } message Command_ResetUserPassword { diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/command_deck_share_list_mine.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/command_deck_share_list_mine.proto deleted file mode 100644 index 75bc30714..000000000 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/command_deck_share_list_mine.proto +++ /dev/null @@ -1,10 +0,0 @@ -syntax = "proto2"; -import "session_commands.proto"; - -// Requests the list of share bundles created by the calling user, so they can -// be reviewed and revoked before they expire. -message Command_DeckShareListMine { - extend SessionCommand { - optional Command_DeckShareListMine ext = 1032; - } -} diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/command_deck_share_remove.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/command_deck_share_remove.proto deleted file mode 100644 index 348996da9..000000000 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/command_deck_share_remove.proto +++ /dev/null @@ -1,11 +0,0 @@ -syntax = "proto2"; -import "session_commands.proto"; - -// Revokes one of the calling user's own share bundles. The referenced items -// are removed by cascade. -message Command_DeckShareRemove { - extend SessionCommand { - optional Command_DeckShareRemove ext = 1033; - } - optional uint32 share_id = 1; -} diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/command_deck_upload.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/command_deck_upload.proto index 1a5d44e9e..a79ac35c2 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/command_deck_upload.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/command_deck_upload.proto @@ -9,9 +9,11 @@ message Command_DeckUpload { optional uint32 deck_id = 2; // to replace an existing deck optional string deck_list = 3; optional bool is_public = 4; // mark the deck public on upload (publish) - // The server derives the banner card and tags from deck_list, so clients only - // need to send the color identity, which cannot be computed server-side. - reserved 5, 6, 8; - reserved "banner_card_name", "banner_card_provider", "tags"; + // Preview metadata computed by the uploading client (see ServerInfo_DeckStorage_File). + optional string banner_card_name = 5; + optional string banner_card_provider = 6; optional string color_identity = 7; + // Comma-separated list of tag names associated with the deck, used to render + // and filter another user's public decks on the client. + optional string tags = 8; } diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/command_get_server_stats.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/command_get_server_stats.proto deleted file mode 100644 index 33c56293b..000000000 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/command_get_server_stats.proto +++ /dev/null @@ -1,8 +0,0 @@ -syntax = "proto2"; -import "developer_commands.proto"; - -message Command_GetServerStats { - extend DeveloperCommand { - optional Command_GetServerStats ext = 1000; - } -} diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/commands.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/commands.proto index 964407819..b6eaf6733 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/commands.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/commands.proto @@ -4,7 +4,6 @@ import "game_commands.proto"; import "room_commands.proto"; import "moderator_commands.proto"; import "admin_commands.proto"; -import "developer_commands.proto"; message CommandContainer { optional uint64 cmd_id = 1; @@ -17,5 +16,4 @@ message CommandContainer { repeated RoomCommand room_command = 102; repeated ModeratorCommand moderator_command = 103; repeated AdminCommand admin_command = 104; - repeated DeveloperCommand developer_command = 105; } diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/developer_commands.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/developer_commands.proto deleted file mode 100644 index bed47d44c..000000000 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/developer_commands.proto +++ /dev/null @@ -1,8 +0,0 @@ -syntax = "proto2"; -message DeveloperCommand { - enum DeveloperCommandType { - GET_SERVER_STATS = 1000; - VIEWLOG_HISTORY = 1001; - } - extensions 100 to max; -} diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/moderator_commands.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/moderator_commands.proto index 4f1e80c27..685408830 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/moderator_commands.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/moderator_commands.proto @@ -1,5 +1,4 @@ syntax = "proto2"; -import "developer_commands.proto"; message ModeratorCommand { enum ModeratorCommandType { BAN_FROM_SERVER = 1000; @@ -81,9 +80,6 @@ message Command_ViewLogHistory { extend ModeratorCommand { optional Command_ViewLogHistory ext = 1005; } - extend DeveloperCommand { - optional Command_ViewLogHistory dev_ext = 1001; - } optional string user_name = 1; // user that created message optional string ip_address = 2; // ip address of user that created message optional string game_name = 3; // client id of user that created the message diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/response.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/response.proto index caf9febde..09f6e5c7b 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/response.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/response.proto @@ -77,14 +77,12 @@ message Response { FORGOT_PASSWORD_REQUEST = 1016; // Response to password reset request PASSWORD_SALT = 1017; // Response containing password salt GET_ADMIN_NOTES = 1018; // Response with admin notes - GET_SERVER_STATS = 1019; // Response with server status statistics REPLAY_LIST = 1100; // Response listing replays REPLAY_DOWNLOAD = 1101; // Response for replay download REPLAY_GET_CODE = 1102; // Response containing replay code DECK_SHARE_CREATE = 1103; // Response to deck share creation DECK_SHARE_LIST = 1104; // Response listing shared decks DECK_SHARE_DOWNLOAD = 1105; // Response for shared deck download - DECK_SHARE_LIST_MINE = 1106; // Response listing the caller's own shares CARD_ART_RULE_LIST = 1200; // Response containing a list of card art rules } diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/response_deck_share_list_mine.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/response_deck_share_list_mine.proto deleted file mode 100644 index e3cbf83b9..000000000 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/response_deck_share_list_mine.proto +++ /dev/null @@ -1,10 +0,0 @@ -syntax = "proto2"; -import "response.proto"; -import "serverinfo_deck_share_summary.proto"; - -message Response_DeckShareListMine { - extend Response { - optional Response_DeckShareListMine ext = 1106; - } - repeated ServerInfo_DeckShareSummary shares = 1; -} diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/response_get_server_stats.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/response_get_server_stats.proto deleted file mode 100644 index bb8ff3c43..000000000 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/response_get_server_stats.proto +++ /dev/null @@ -1,41 +0,0 @@ -syntax = "proto2"; -import "response.proto"; - -message CommandStats { - optional uint32 kind_index = 1; // 0=session, 1=room, 2=game, 3=moderator, 4=admin, 5=developer - optional uint32 extension_number = 2; // protobuf extension number within the kind - optional string command_name = 3; // e.g. "session/Command_Ping" - optional uint64 count = 4; // number of times observed - optional uint64 total_ms = 5; // cumulative processing milliseconds -} - -message Response_GetServerStats { - extend Response { - optional Response_GetServerStats ext = 1220; - } - - optional uint64 users_count = 1; - optional uint64 mods_count = 2; - optional uint64 games_count = 3; - - // Traffic recorded during the last status update tick - optional uint64 tx_bytes = 4; - optional uint64 rx_bytes = 5; - - optional uint64 uptime_secs = 6; - optional uint64 timest = 7; // unix timestamp of the snapshot - - // Live metrics from MetricsRegistry (reset on server restart) - optional uint64 cards_in_games = 8; - optional uint64 eventloop_stalls_total = 9; - optional uint64 eventloop_last_stall_ms = 10; - optional uint64 eventloop_max_stall_ms = 11; - optional uint64 total_commands = 12; - optional uint64 total_command_time_ms = 13; - optional int32 active_command_types = 14; - optional uint64 game_start_count = 15; - optional uint64 game_start_total_ms = 16; - - // Per-command breakdown (only types with count > 0) - repeated CommandStats command_stats = 20; -} diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_deck_share_summary.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_deck_share_summary.proto deleted file mode 100644 index 43273a54f..000000000 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_deck_share_summary.proto +++ /dev/null @@ -1,10 +0,0 @@ -syntax = "proto2"; - -// A share bundle created by a user, as reported by a "list my shares" query. -message ServerInfo_DeckShareSummary { - optional uint32 id = 1; - optional string name = 2; - optional uint64 creation_time = 3; - optional uint64 expires_at = 4; - optional uint32 item_count = 5; -} diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_deckstorage.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_deckstorage.proto index b04d676d8..a77dc9484 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_deckstorage.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_deckstorage.proto @@ -8,9 +8,10 @@ message ServerInfo_DeckStorage_File { optional string banner_card_name = 3; optional string banner_card_provider = 4; optional string color_identity = 5; - // Tag names associated with the deck. Empty for decks uploaded before the - // tags column existed. - repeated string tags = 6; + // Comma-separated list of tag names, matching the corresponding + // ServerInfo_DeckStorage_File upload metadata. Empty for decks uploaded + // before the tags column existed. + optional string tags = 6; } message ServerInfo_DeckStorage_Folder { diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_user.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_user.proto index ea3f56705..98cc3ce6a 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_user.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_user.proto @@ -8,7 +8,6 @@ message ServerInfo_User { IsModerator = 4; IsAdmin = 8; IsJudge = 16; - IsDeveloper = 32; }; message PawnColorsOverride { optional string left_side = 1; diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/session_commands.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/session_commands.proto index 4b7fe9c85..c4b47f10f 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/session_commands.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/session_commands.proto @@ -34,8 +34,6 @@ message SessionCommand { DECK_LIST_OTHER_USER = 1029; DECK_SET_VISIBILITY = 1030; DECK_DOWNLOAD_PUBLIC = 1031; - DECK_SHARE_LIST_MINE = 1032; - DECK_SHARE_REMOVE = 1033; REPLAY_LIST = 1100; REPLAY_DOWNLOAD = 1101; REPLAY_MODIFY_MATCH = 1102; 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/chat_settings.cpp b/libcockatrice_settings/libcockatrice/settings/chat_settings.cpp index 974173371..5e1102473 100644 --- a/libcockatrice_settings/libcockatrice/settings/chat_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/chat_settings.cpp @@ -65,11 +65,6 @@ bool ChatSettings::getRoomHistory() const return getValue("roomHistory", QString(), QString(), true).toBool(); } -bool ChatSettings::getIgnoreAllPrivateMessages() const -{ - return getValue("ignoreAllPrivateMessages", QString(), QString(), false).toBool(); -} - QString ChatSettings::getHighlightWords() const { return getValue("highlightWords").toString(); @@ -136,11 +131,6 @@ void ChatSettings::setRoomHistory(bool _roomHistory) setValue(_roomHistory, "roomHistory"); } -void ChatSettings::setIgnoreAllPrivateMessages(bool _ignoreAllPrivateMessages) -{ - setValue(_ignoreAllPrivateMessages, "ignoreAllPrivateMessages"); -} - void ChatSettings::setHighlightWords(const QString &_highlightWords) { setValue(_highlightWords, "highlightWords"); diff --git a/libcockatrice_settings/libcockatrice/settings/chat_settings.h b/libcockatrice_settings/libcockatrice/settings/chat_settings.h index 9566671e2..7cf4be3f6 100644 --- a/libcockatrice_settings/libcockatrice/settings/chat_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/chat_settings.h @@ -23,7 +23,6 @@ public: [[nodiscard]] bool getShowMessagePopup() const override; [[nodiscard]] bool getShowMentionPopup() const override; [[nodiscard]] bool getRoomHistory() const override; - [[nodiscard]] bool getIgnoreAllPrivateMessages() const override; [[nodiscard]] QString getHighlightWords() const override; void setChatMention(bool _chatMention); @@ -38,7 +37,6 @@ public: void setShowMessagePopups(bool _showMessagePopups); void setShowMentionPopups(bool _showMentionPopups); void setRoomHistory(bool _roomHistory); - void setIgnoreAllPrivateMessages(bool _ignoreAllPrivateMessages); void setHighlightWords(const QString &_highlightWords); signals: 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/CMakeLists.txt b/oracle/CMakeLists.txt index 392184b6e..68c4709bb 100644 --- a/oracle/CMakeLists.txt +++ b/oracle/CMakeLists.txt @@ -28,7 +28,6 @@ set(oracle_SOURCES ../cockatrice/src/client/settings/card_counter_settings.cpp ../cockatrice/src/client/settings/shortcuts_settings.cpp ../cockatrice/src/client/network/update/client/release_channel.cpp - ../cockatrice/src/interface/pixel_map_generator.cpp ../cockatrice/src/interface/theme_config.cpp ../cockatrice/src/interface/theme_manager.cpp ../cockatrice/src/interface/widgets/quick_settings/settings_button_widget.cpp diff --git a/oracle/src/main.cpp b/oracle/src/main.cpp index 0f962f9be..bb88153b1 100644 --- a/oracle/src/main.cpp +++ b/oracle/src/main.cpp @@ -50,8 +50,8 @@ int main(int argc, char *argv[]) QApplication app(argc, argv); QCoreApplication::setOrganizationName("Cockatrice"); - QCoreApplication::setOrganizationDomain("Cockatrice"); - // This can't be changed, as it influences the default save path for cards.xml + QCoreApplication::setOrganizationDomain("cockatrice"); + // this can't be changed, as it influences the default save path for cards.xml QCoreApplication::setApplicationName("Cockatrice"); // If the program is opened with the -s flag, it will only do spoilers. Otherwise it will do MTGJSON/Tokens @@ -83,7 +83,7 @@ int main(int argc, char *argv[]) QIcon icon("theme:appicon.svg"); wizard.setWindowIcon(icon); // set name of the app desktop file; used by wayland to load the window icon - QGuiApplication::setDesktopFileName("Oracle"); + QGuiApplication::setDesktopFileName("oracle"); wizard.show(); diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index 88b522197..d745b250c 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())) { @@ -85,16 +47,8 @@ static CardSet::Priority getSetPriority(const QString &setType, const QString &s bool OracleImporter::readSetsFromByteArray(QByteArray data) { - const RawJson::ScanProgressCallback progress = - progressReporting - ? [this]( - qsizetype bytesRead, - qsizetype - totalBytes) { emit dataReadProgress(static_cast(bytesRead), static_cast(totalBytes)); } - : RawJson::ScanProgressCallback{}; - RawJson::ScanError error; - const QList ranges = RawJson::scanSetRanges(data, &error, progress); + const QList ranges = RawJson::scanSetRanges(data, &error); if (error.isError()) { qDebug() << "error: RawJson::scanSetRanges():" << error.message; return false; @@ -142,30 +96,16 @@ bool OracleImporter::readSetsFromByteArray(QByteArray data) return true; } -/** - * The priority order used to pick a card's main type when a card has multiple - * types (e.g. "Artifact Creature") or multiple faces (e.g. split/adventure cards). - * A lower index means a higher priority. - */ -static const QStringList MAIN_CARD_TYPE_PRIORITY = {"Planeswalker", "Creature", "Land", "Sorcery", - "Instant", "Artifact", "Enchantment"}; - -/** - * Returns the priority (index) of the given main card type. Known types map to their - * position in {@link mainCardTypePriority()}, unknown types map to -1 (lowest priority). - */ -static int mainCardTypePriority(const QString &mainCardType) -{ - return MAIN_CARD_TYPE_PRIORITY.indexOf(mainCardType); -} - static QString getMainCardType(const QStringList &typeList) { if (typeList.isEmpty()) { return {}; } - for (const auto &type : MAIN_CARD_TYPE_PRIORITY) { + static const QStringList typePriority = {"Planeswalker", "Creature", "Land", "Sorcery", + "Instant", "Artifact", "Enchantment"}; + + for (const auto &type : typePriority) { if (typeList.contains(type)) { return type; } @@ -292,101 +232,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 +375,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 +424,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 +437,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 +446,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(); @@ -642,15 +460,8 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QJson properties.insert(prop, thisCardPropertyValue); } else if (prop == "colors" || prop == "coloridentity") { // the card is both colors properties.insert(prop, originalPropertyValue + thisCardPropertyValue); - } else if (prop == "maintype") { - // Use the same priority as getMainCardType() to pick the - // "best" type across faces — e.g. Creature over Instant - // for adventure cards like Bonecrusher Giant. - int currentPriority = mainCardTypePriority(originalPropertyValue); - int newPriority = mainCardTypePriority(thisCardPropertyValue); - if (newPriority >= 0 && (currentPriority < 0 || newPriority < currentPriority)) { - properties.insert(prop, thisCardPropertyValue); - } + } else if (prop == "maintype") { // don't create maintypes with //es in them + continue; } else { properties.insert(prop, originalPropertyValue + splitCardPropSeparator + thisCardPropertyValue); @@ -660,18 +471,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++; } @@ -752,8 +551,6 @@ int OracleImporter::startImport() { static ICardSetPriorityController *noOpController = new NoopCardSetPriorityController(); - importCancelled.storeRelease(0); - // Pre-allocate the cards hash to avoid rehashing during import. Keys are // distinct card names while raw ranges only count printings (AllPrintings // ~100k printings vs ~35k names), so this over-reserves somewhat; an exact @@ -773,12 +570,6 @@ int OracleImporter::startImport() int setIndex = 0; for (const SetToDownload &curSetToParse : allSets) { - if (importCancelled.loadAcquire()) { - // The wizard was closed mid-import: stop at the next set boundary so - // the caller can wait for this future without processing every set. - break; - } - CardSetPtr newSet = CardSet::newInstance(noOpController, curSetToParse.getShortName(), curSetToParse.getLongName(), curSetToParse.getSetType(), curSetToParse.getReleaseDate(), curSetToParse.getPriority()); @@ -826,8 +617,6 @@ int OracleImporter::startImport() emit setIndexChanged(numCardsInSet, setIndex, curSetToParse.getLongName()); } - applyLocalizedData(); - emit setIndexChanged(0, setIndex, QString()); // total number of sets @@ -853,6 +642,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..8cb30ca40 100644 --- a/oracle/src/oracleimporter.h +++ b/oracle/src/oracleimporter.h @@ -3,7 +3,6 @@ #include "raw_json_scanner.h" -#include #include #include #include @@ -107,8 +106,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 +115,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 +127,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 @@ -172,118 +155,24 @@ private: */ QByteArray rawSetsData; - /** - * Whether readSetsFromByteArray() should report scan progress via - * dataReadProgress. A background run routes that signal to stdout (for the - * hosting Cockatrice client to parse); the flag exists to skip the scanner - * instrumentation entirely when no consumer needs it. - */ - bool progressReporting = true; - - /** - * Atomic "please stop importing" flag. startImport() checks it between sets - * so a wizard being closed mid-import can be torn down without waiting for - * the whole import (or racing it). - */ - 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); public: explicit OracleImporter(QObject *parent = nullptr); - /** - * @brief Controls whether readSetsFromByteArray() instruments the raw scan. - * - * When enabled (the default) the raw scanner reports progress via - * dataReadProgress(), which an interactive wizard shows on its progress bar - * and a background run routes to stdout for the hosting client. Switch it - * off only when nothing will consume scan progress. - */ - void setProgressReporting(bool enabled) - { - 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. */ bool readSetsFromByteArray(QByteArray data); int startImport(); - /** - * @brief Requests an in-flight startImport() to stop at the next set boundary. - * - * Works by setting an atomic flag that startImport() polls between sets, so - * cancelImport() followed by a short waitForFinished() on the running future is - * safe the moment the wizard is about to be destroyed. - */ - void cancelImport() - { - 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..11200dfc5 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); @@ -114,24 +110,6 @@ void OracleWizard::accept() QDialog::accept(); } -void OracleWizard::reject() -{ - // The wizard is being closed while a page may still run a worker on the - // importer. Ask it to stop before the wizard (and the importer child) is - // destroyed, so the worker thread never touches freed memory. - if (auto *active = dynamic_cast(currentPage())) { - active->cancelWork(); - } - QWizard::reject(); -} - -void OracleWizard::runInBackground() -{ - backgroundMode = true; - hide(); - currentPage()->initializePage(); -} - void OracleWizard::enableButtons() { button(QWizard::NextButton)->setDisabled(false); diff --git a/oracle/src/oraclewizard.h b/oracle/src/oraclewizard.h index 9a509ce5e..78427175c 100644 --- a/oracle/src/oraclewizard.h +++ b/oracle/src/oraclewizard.h @@ -23,7 +23,6 @@ class OracleWizard : public QWizard public: explicit OracleWizard(QWidget *parent = nullptr); void accept() override; - void reject() override; void enableButtons(); void disableButtons(); void retranslateUi(); @@ -53,7 +52,12 @@ public: } bool saveTokensToFile(const QString &fileName); - void runInBackground(); + void runInBackground() + { + backgroundMode = true; + hide(); + currentPage()->initializePage(); + } public: OracleImporter *importer; diff --git a/oracle/src/pages.cpp b/oracle/src/pages.cpp index df0c3f51d..94e662ffe 100644 --- a/oracle/src/pages.cpp +++ b/oracle/src/pages.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -21,17 +20,14 @@ #include #include #include -#include #include #include #include #include #include #include -#include #include #include -#include #include #ifdef HAS_LZMA @@ -57,115 +53,6 @@ #define ALLSETS_URL "https://www.mtgjson.com/api/v5/AllPrintings.json" #endif -/** - * @brief Emits one machine-readable background-run progress line to stdout. - * - * Used only in background mode, so the hosting Cockatrice client can parse these - * lines to drive a determinate progress bar. stderr stays reserved for - * human-readable log output. - */ -static void emitBackgroundProgress(const char *stage, qint64 done, qint64 total) -{ - QTextStream out(stdout); - out << "PROGRESS " << stage << ' ' << done << ' ' << total << '\n'; - out.flush(); -} - -namespace -{ - -/** - * @brief Decompresses and dispatches a sets-file payload on a worker thread. - * - * Iteratively unwraps xz/zip compression, then either hands the JSON to the - * importer (which reports scan progress via dataReadProgress) or returns the raw - * XML for the plain-XML path. Must never touch the wizard or the page: the caller - * consumes the returned LoadSetsResult on the UI thread in importFinished(). - */ -LoadSetsResult loadSetsData(const QPointer &importer, QByteArray data) -{ - LoadSetsResult result; - - while (true) { - if (data.startsWith(XZ_SIGNATURE)) { -#ifdef HAS_LZMA - QBuffer inBuffer(&data); - QByteArray decompressed; - QBuffer outBuffer(&decompressed); - inBuffer.open(QBuffer::ReadOnly); - outBuffer.open(QBuffer::WriteOnly); - XzDecompressor xz; - if (!xz.decompress(&inBuffer, &outBuffer)) { - result.errorMessage = LoadSetsPage::tr("Xz extraction failed."); - result.offerUncompressedFallback = true; - return result; - } - data = decompressed; - continue; -#else - result.errorMessage = - LoadSetsPage::tr("Sorry, this version of Oracle does not support xz compressed files."); - result.offerUncompressedFallback = true; - return result; -#endif - } - - if (data.startsWith(ZIP_SIGNATURE)) { -#ifdef HAS_ZLIB - QBuffer inBuffer(&data); - UnZip uz; - const UnZip::ErrorCode openEc = uz.openArchive(&inBuffer); - if (openEc != UnZip::Ok) { - result.errorMessage = LoadSetsPage::tr("Failed to open Zip archive: %1.").arg(uz.formatError(openEc)); - result.offerUncompressedFallback = true; - return result; - } - if (uz.fileList().size() != 1) { - result.errorMessage = - LoadSetsPage::tr("Zip extraction failed: the Zip archive doesn't contain exactly one file."); - result.offerUncompressedFallback = true; - return result; - } - const QString fileName = uz.fileList().at(0); - QByteArray decompressed; - QBuffer outBuffer(&decompressed); - outBuffer.open(QBuffer::ReadWrite); - const UnZip::ErrorCode ec = uz.extractFile(fileName, &outBuffer); - uz.closeArchive(); - if (ec != UnZip::Ok) { - result.errorMessage = LoadSetsPage::tr("Zip extraction failed: %1.").arg(uz.formatError(ec)); - result.offerUncompressedFallback = true; - return result; - } - data = decompressed; - continue; -#else - result.errorMessage = LoadSetsPage::tr("Sorry, this version of Oracle does not support zipped files."); - result.offerUncompressedFallback = true; - return result; -#endif - } - break; - } - - if (data.startsWith("<")) { - result.ok = true; - result.plainXml = true; - result.xmlData = std::move(data); - return result; - } - - if (data.startsWith("{")) { - result.ok = importer && importer->readSetsFromByteArray(std::move(data)); - return result; - } - - result.errorMessage = LoadSetsPage::tr("Failed to interpret downloaded data."); - return result; -} - -} // namespace - #define TOKENS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Token/master/tokens.xml" #define SPOILERS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/spoiler.xml" @@ -295,11 +182,6 @@ LoadSetsPage::LoadSetsPage(QWidget *parent) : OracleWizardPage(parent) setLayout(layout); } -bool LoadSetsPage::isComplete() const -{ - return !loadActive; -} - void LoadSetsPage::initializePage() { urlLineEdit->setText(wizard()->settings->value("allsetsurl", ALLSETS_URL).toString()); @@ -378,7 +260,7 @@ bool LoadSetsPage::validatePage() return false; } - progressLabel->setText(tr("Downloading (0 MB)")); + progressLabel->setText(tr("Downloading (0MB)")); // show an infinite progressbar progressBar->setMaximum(0); progressBar->setMinimum(0); @@ -397,13 +279,18 @@ bool LoadSetsPage::validatePage() return false; } + if (!setsFile.open(QIODevice::ReadOnly)) { + QMessageBox::critical(nullptr, tr("Error"), tr("Cannot open file '%1'.").arg(fileLineEdit->text())); + return false; + } + wizard()->disableButtons(); setEnabled(false); wizard()->setCardSourceUrl(setsFile.fileName()); wizard()->setCardSourceVersion("unknown"); - readSetsFromFile(setsFile.fileName()); + readSetsFromByteArray(setsFile.readAll()); } return false; @@ -452,14 +339,11 @@ void LoadSetsPage::downloadSetsFile(const QUrl &url) void LoadSetsPage::actDownloadProgressSetsFile(qint64 received, qint64 total) { - if (wizard()->backgroundMode) { - emitBackgroundProgress("download", received, total); - } if (total > 0) { progressBar->setMaximum(static_cast(total)); progressBar->setValue(static_cast(received)); } - progressLabel->setText(tr("Downloading (%1 MB)").arg((int)received / (1024 * 1024))); + progressLabel->setText(tr("Downloading (%1MB)").arg((int)received / (1024 * 1024))); } void LoadSetsPage::actDownloadFinishedSetsFile() @@ -500,97 +384,106 @@ void LoadSetsPage::actDownloadFinishedSetsFile() reply->deleteLater(); } -void LoadSetsPage::updateParsingProgress(int bytesRead, int totalBytes) +void LoadSetsPage::readSetsFromByteArray(QByteArray _data) { - if (totalBytes <= 0) { - return; - } - progressBar->setRange(0, totalBytes); - progressBar->setValue(bytesRead); - const int percent = static_cast((100.0 * bytesRead) / totalBytes); - progressLabel->setText(tr("Parsing file (%1%)").arg(percent)); -} - -void LoadSetsPage::scanProgressToStdout(int bytesRead, int totalBytes) -{ - emitBackgroundProgress("scan", bytesRead, totalBytes); -} - -void LoadSetsPage::beginLoadSets(bool compressedFile) -{ - // Show an infinite progressbar while the worker decompresses; the scan - // steals the label via dataReadProgress as soon as it starts. + // show an infinite progressbar progressBar->setMaximum(0); progressBar->setMinimum(0); progressBar->setValue(0); - progressLabel->setText(compressedFile ? tr("Extracting file...") : tr("Parsing file")); + progressLabel->setText(tr("Parsing file")); progressLabel->show(); progressBar->show(); - // Keep Next disabled (via completeChanged) until the worker reports in; - // updateButtonStates() re-evaluates button state whenever we re-enable. - loadActive = true; - emit completeChanged(); - wizard()->downloadedPlainXml = false; wizard()->xmlData.clear(); - - if (wizard()->backgroundMode) { - connect(wizard()->importer, &OracleImporter::dataReadProgress, this, &LoadSetsPage::scanProgressToStdout, - Qt::UniqueConnection); - } else { - connect(wizard()->importer, &OracleImporter::dataReadProgress, this, &LoadSetsPage::updateParsingProgress, - Qt::UniqueConnection); - } + readSetsFromByteArrayRef(_data); } -void LoadSetsPage::readSetsFromByteArray(QByteArray _data) +void LoadSetsPage::readSetsFromByteArrayRef(QByteArray &_data) { - const bool compressed = _data.startsWith(XZ_SIGNATURE) || _data.startsWith(ZIP_SIGNATURE); - beginLoadSets(compressed); - - // Decompress and scan off the UI thread so a large download can't freeze the window. - const QPointer importer = wizard()->importer; - future = QtConcurrent::run( - [importer, data = std::move(_data)]() mutable { return loadSetsData(importer, std::move(data)); }); - watcher.setFuture(future); -} - -void LoadSetsPage::readSetsFromFile(const QString &fileName) -{ - // Peek at the header on the UI thread so the status text can distinguish - // "Extracting file..." from a plain JSON parse; the full read happens in the worker. - QFile headerFile(fileName); - bool compressed = false; - if (headerFile.open(QIODevice::ReadOnly)) { - const QByteArray header = headerFile.read(6); - compressed = header.startsWith(XZ_SIGNATURE) || header.startsWith(ZIP_SIGNATURE); - } - beginLoadSets(compressed); - - // Read, decompress and scan off the UI thread (a plain JSON can be hundreds - // of MB, so even the read itself must not block the window). - const QPointer importer = wizard()->importer; - future = QtConcurrent::run([importer, fileName]() mutable -> LoadSetsResult { - QFile file(fileName); - if (!file.open(QIODevice::ReadOnly)) { - LoadSetsResult readError; - readError.errorMessage = LoadSetsPage::tr("Cannot open file '%1'.").arg(fileName); - return readError; + // unzip the file if needed + if (_data.startsWith(XZ_SIGNATURE)) { +#ifdef HAS_LZMA + // zipped file + auto *inBuffer = new QBuffer(&_data); + auto newData = QByteArray(); + auto *outBuffer = new QBuffer(&newData); + inBuffer->open(QBuffer::ReadOnly); + outBuffer->open(QBuffer::WriteOnly); + XzDecompressor xz; + if (!xz.decompress(inBuffer, outBuffer)) { + zipDownloadFailed(tr("Xz extraction failed.")); + return; } - return loadSetsData(importer, file.readAll()); - }); - watcher.setFuture(future); -} + _data.clear(); + readSetsFromByteArrayRef(newData); + return; +#else + zipDownloadFailed(tr("Sorry, this version of Oracle does not support xz compressed files.")); -void LoadSetsPage::cancelWork() -{ - // The scan is short-lived; just wait it out before the wizard (and its - // importer) can be torn down underneath the worker thread. - if (future.isRunning()) { - future.cancel(); - watcher.cancel(); - future.waitForFinished(); + wizard()->enableButtons(); + setEnabled(true); + progressLabel->hide(); + progressBar->hide(); + return; +#endif + } else if (_data.startsWith(ZIP_SIGNATURE)) { +#ifdef HAS_ZLIB + // zipped file + auto *inBuffer = new QBuffer(&_data); + auto newData = QByteArray(); + auto *outBuffer = new QBuffer(&newData); + QString fileName; + UnZip::ErrorCode ec; + UnZip uz; + + ec = uz.openArchive(inBuffer); + if (ec != UnZip::Ok) { + zipDownloadFailed(tr("Failed to open Zip archive: %1.").arg(uz.formatError(ec))); + return; + } + + if (uz.fileList().size() != 1) { + zipDownloadFailed(tr("Zip extraction failed: the Zip archive doesn't contain exactly one file.")); + return; + } + fileName = uz.fileList().at(0); + + outBuffer->open(QBuffer::ReadWrite); + ec = uz.extractFile(fileName, outBuffer); + if (ec != UnZip::Ok) { + zipDownloadFailed(tr("Zip extraction failed: %1.").arg(uz.formatError(ec))); + uz.closeArchive(); + return; + } + _data.clear(); + readSetsFromByteArrayRef(newData); + return; +#else + zipDownloadFailed(tr("Sorry, this version of Oracle does not support zipped files.")); + + wizard()->enableButtons(); + setEnabled(true); + progressLabel->hide(); + progressBar->hide(); + return; +#endif + } else if (_data.startsWith("{")) { + // Start the computation. + jsonData = std::move(_data); + future = QtConcurrent::run([this] { return wizard()->importer->readSetsFromByteArray(std::move(jsonData)); }); + watcher.setFuture(future); + } else if (_data.startsWith("<")) { + // save xml file and don't do any processing + wizard()->downloadedPlainXml = true; + wizard()->xmlData = std::move(_data); + importFinished(); + } else { + wizard()->enableButtons(); + setEnabled(true); + progressLabel->hide(); + progressBar->hide(); + QMessageBox::critical(this, tr("Error"), tr("Failed to interpret downloaded data.")); } } @@ -616,65 +509,17 @@ void LoadSetsPage::zipDownloadFailed(const QString &message) void LoadSetsPage::importFinished() { - loadActive = false; - emit completeChanged(); wizard()->enableButtons(); setEnabled(true); + progressLabel->hide(); + progressBar->hide(); - const LoadSetsResult result = watcher.result(); - - if (result.plainXml) { - wizard()->downloadedPlainXml = true; - wizard()->xmlData = result.xmlData; + if (wizard()->downloadedPlainXml || watcher.future().result()) { + wizard()->next(); + } else { + QMessageBox::critical(this, tr("Error"), + tr("The file was retrieved successfully, but it does not contain any sets data.")); } - - if (wizard()->backgroundMode) { - progressLabel->hide(); - progressBar->hide(); - if (!result.errorMessage.isEmpty()) { - qWarning() << result.errorMessage; - } else if (!result.ok && !result.plainXml) { - qWarning() << tr("The file was retrieved successfully, but it does not contain any sets data."); - } - emit readyToContinue(); - return; - } - - const auto fail = [this](const QString &message) { - progressLabel->hide(); - progressBar->hide(); - QMessageBox::critical(this, tr("Error"), message); - }; - - if (!result.errorMessage.isEmpty()) { - if (result.offerUncompressedFallback) { - zipDownloadFailed(result.errorMessage); - return; - } - fail(result.errorMessage); - return; - } - - if (!result.ok && !result.plainXml) { - fail(tr("The file was retrieved successfully, but it does not contain any sets data.")); - return; - } - - // Snap the bar to 100% and hold it there for a moment so the completed state - // is actually visible before the next page's own import progress takes over - // (a zero-length deferral can still fire before the repaint is delivered). - progressBar->setMaximum(1); - progressBar->setValue(1); - progressLabel->setText(tr("Parsing file (100%)")); - QTimer::singleShot(500, this, [this] { - if (wizard()->currentPage() == this) { - // Leave the page pristine: hide the completed load bar so a later - // Back from the save page doesn't show stale progress. - progressLabel->hide(); - progressBar->hide(); - wizard()->next(); - } - }); } SaveSetsPage::SaveSetsPage(QWidget *parent) : OracleWizardPage(parent) @@ -682,113 +527,49 @@ SaveSetsPage::SaveSetsPage(QWidget *parent) : OracleWizardPage(parent) pathLabel = new QLabel(this); saveLabel = new QLabel(this); - progressBar = new QProgressBar(this); - progressBar->hide(); - defaultPathCheckBox = new QCheckBox(this); messageLog = new QTextEdit(this); messageLog->setReadOnly(true); auto *layout = new QGridLayout(this); - layout->addWidget(progressBar, 0, 0); - layout->addWidget(messageLog, 1, 0); - layout->addWidget(saveLabel, 2, 0); - layout->addWidget(pathLabel, 3, 0); - layout->addWidget(defaultPathCheckBox, 4, 0); + layout->addWidget(messageLog, 0, 0); + layout->addWidget(saveLabel, 1, 0); + layout->addWidget(pathLabel, 2, 0); + layout->addWidget(defaultPathCheckBox, 3, 0); setLayout(layout); } -bool SaveSetsPage::isComplete() const -{ - return !importActive; -} - void SaveSetsPage::cleanupPage() { - cancelWork(); - disconnect(wizard()->importer, &OracleImporter::setIndexChanged, this, &SaveSetsPage::updateTotalProgress); - disconnect(&importWatcher, &QFutureWatcher::finished, this, &SaveSetsPage::importFinished); wizard()->importer->clear(); + disconnect(wizard()->importer, &OracleImporter::setIndexChanged, nullptr, nullptr); } void SaveSetsPage::initializePage() { + messageLog->clear(); + retranslateUi(); if (wizard()->downloadedPlainXml) { messageLog->hide(); - progressBar->hide(); - if (wizard()->backgroundMode) { - emit readyToContinue(); + } else { + messageLog->show(); + connect(wizard()->importer, &OracleImporter::setIndexChanged, this, &SaveSetsPage::updateTotalProgress); + + int setsImported = wizard()->importer->startImport(); + + // JSON data no longer needed after CardInfo objects are built + wizard()->importer->releaseSetData(); + + if (setsImported == 0) { + QMessageBox::critical(this, tr("Error"), tr("No set has been imported.")); } - return; - } - - messageLog->clear(); - messageLog->show(); - progressBar->show(); - - totalSets = wizard()->importer->getSets().size(); - progressBar->setRange(0, totalSets); - progressBar->setValue(0); - - connect(wizard()->importer, &OracleImporter::setIndexChanged, this, &SaveSetsPage::updateTotalProgress, - Qt::UniqueConnection); - connect(&importWatcher, &QFutureWatcher::finished, this, &SaveSetsPage::importFinished, Qt::UniqueConnection); - - wizard()->disableButtons(); - importActive = true; - emit completeChanged(); - - const QPointer importer = wizard()->importer; - importFuture = QtConcurrent::run([importer] { return importer ? importer->startImport() : 0; }); - importWatcher.setFuture(importFuture); -} - -void SaveSetsPage::cancelWork() -{ - if (!importActive) { - return; - } - // Ask the worker to stop at the next set boundary, then wait it out so the - // wizard (and the importer it owns) is never torn down under a running thread. - importActive = false; - emit completeChanged(); - wizard()->importer->cancelImport(); - importFuture.cancel(); - importWatcher.cancel(); - importFuture.waitForFinished(); -} - -void SaveSetsPage::importFinished() -{ - if (!importActive) { - return; - } - importActive = false; - emit completeChanged(); - - wizard()->enableButtons(); - - const int setsImported = importWatcher.result(); - const QPointer importer = wizard()->importer; - if (importer) { - importer->releaseSetData(); } if (wizard()->backgroundMode) { - if (setsImported == 0) { - qWarning() << tr("No set has been imported."); - } emit readyToContinue(); - return; - } - - progressBar->setValue(progressBar->maximum()); - - if (setsImported == 0) { - QMessageBox::critical(this, tr("Error"), tr("No set has been imported.")); } } @@ -796,7 +577,7 @@ void SaveSetsPage::retranslateUi() { setTitle(tr("Sets imported")); if (wizard()->downloadedPlainXml) { - setSubTitle(tr("A Cockatrice card database file of %1 MB has been downloaded.") + setSubTitle(tr("A cockatrice database file of %1 MB has been downloaded.") .arg(qRound(wizard()->xmlData.size() / 1000000.0))); } else { setSubTitle(tr("The following sets have been found:")); @@ -810,28 +591,13 @@ void SaveSetsPage::retranslateUi() setButtonText(QWizard::NextButton, tr("&Save")); } -void SaveSetsPage::updateTotalProgress(int cardsImported, int setIndex, const QString &setName) +void SaveSetsPage::updateTotalProgress(int cardsImported, int /* setIndex */, const QString &setName) { - if (!importActive) { - return; - } if (setName.isEmpty()) { - progressBar->setValue(progressBar->maximum()); - const int cardCount = wizard()->importer->getCardList().size(); - if (wizard()->backgroundMode) { - qInfo() << tr("Import finished: %1 cards.").arg(cardCount); - emitBackgroundProgress("import", totalSets, totalSets); - } else { - messageLog->append("" + tr("Import finished: %1 cards.").arg(cardCount) + ""); - } + messageLog->append("" + tr("Import finished: %1 cards.").arg(wizard()->importer->getCardList().size()) + + ""); } else { - progressBar->setValue(setIndex); - if (wizard()->backgroundMode) { - qInfo() << tr("%1: %2 cards imported").arg(setName).arg(cardsImported); - emitBackgroundProgress("import", setIndex, totalSets); - } else { - messageLog->append(tr("%1: %2 cards imported").arg(setName).arg(cardsImported)); - } + messageLog->append(tr("%1: %2 cards imported").arg(setName).arg(cardsImported)); } messageLog->verticalScrollBar()->setValue(messageLog->verticalScrollBar()->maximum()); @@ -977,4 +743,4 @@ void LoadSpoilersPage::retranslateUi() pathLabel->setText(tr("The spoiler database will be saved at the following location:") + "
" + SettingsCache::instance().getSpoilerCardDatabasePath()); defaultPathCheckBox->setText(tr("Save to a custom path (not recommended)")); -} +} \ No newline at end of file diff --git a/oracle/src/pages.h b/oracle/src/pages.h index d0a9fcd43..066cc2e1b 100644 --- a/oracle/src/pages.h +++ b/oracle/src/pages.h @@ -3,10 +3,8 @@ #include "pagetemplates.h" -#include #include #include -#include #include #include #include @@ -59,31 +57,19 @@ protected: void initializePage() override; }; -/** @brief Result of a worker-thread sets-file load (read + decompress + dispatch). */ -struct LoadSetsResult -{ - bool ok = false; ///< JSON scan produced set data (or plain XML was handled) - bool plainXml = false; ///< input was a plain Cockatrice XML database - QByteArray xmlData; ///< raw XML for the plain-XML path - QString errorMessage; ///< set when the input could not be processed - bool offerUncompressedFallback = false; ///< decompression-only failure: offer the uncompressed URL -}; - class LoadSetsPage : public OracleWizardPage { Q_OBJECT public: explicit LoadSetsPage(QWidget *parent = nullptr); void retranslateUi() override; - bool isComplete() const override; protected: void initializePage() override; bool validatePage() override; void readSetsFromByteArray(QByteArray _data); - void readSetsFromFile(const QString &fileName); + void readSetsFromByteArrayRef(QByteArray &_data); void downloadSetsFile(const QUrl &url); - void cancelWork() override; private: QRadioButton *urlRadioButton; @@ -95,19 +81,15 @@ private: QLabel *progressLabel; QProgressBar *progressBar; - QFutureWatcher watcher; - QFuture future; - bool loadActive = false; - - void beginLoadSets(bool compressedFile = false); + QFutureWatcher watcher; + QFuture future; + QByteArray jsonData; private slots: void actLoadSetsFile(); void actRestoreDefaultUrl(); void actDownloadProgressSetsFile(qint64 received, qint64 total); void actDownloadFinishedSetsFile(); - void updateParsingProgress(int bytesRead, int totalBytes); - void scanProgressToStdout(int bytesRead, int totalBytes); void importFinished(); void zipDownloadFailed(const QString &message); }; @@ -118,28 +100,19 @@ class SaveSetsPage : public OracleWizardPage public: explicit SaveSetsPage(QWidget *parent = nullptr); void retranslateUi() override; - bool isComplete() const override; private: QTextEdit *messageLog; - QProgressBar *progressBar; QCheckBox *defaultPathCheckBox; QLabel *pathLabel; QLabel *saveLabel; - QFutureWatcher importWatcher; - QFuture importFuture; - int totalSets = 0; - bool importActive = false; - protected: void initializePage() override; void cleanupPage() override; bool validatePage() override; - void cancelWork() override; private slots: - void importFinished(); void updateTotalProgress(int cardsImported, int setIndex, const QString &setName); }; diff --git a/oracle/src/pagetemplates.h b/oracle/src/pagetemplates.h index ccad7ec62..6e79c867e 100644 --- a/oracle/src/pagetemplates.h +++ b/oracle/src/pagetemplates.h @@ -20,14 +20,6 @@ public: } virtual void retranslateUi() = 0; - /** - * @brief Asks an active page to stop any background worker before the wizard - * (and its importer) can be torn down underneath it. Default is a no-op. - */ - virtual void cancelWork() - { - } - signals: void readyToContinue(); diff --git a/oracle/src/parsehelpers.cpp b/oracle/src/parsehelpers.cpp index ee7c4eb2e..a97bf67c9 100644 --- a/oracle/src/parsehelpers.cpp +++ b/oracle/src/parsehelpers.cpp @@ -20,7 +20,7 @@ * Note that "...enters tapped unless..." returns false. * * @param name The name of the card - * @param text The Oracle text of the card + * @param text The oracle text of the card */ bool parseCipt(const QString &name, const QString &text) { diff --git a/oracle/src/raw_json_scanner.cpp b/oracle/src/raw_json_scanner.cpp index 8c4633598..db972491c 100644 --- a/oracle/src/raw_json_scanner.cpp +++ b/oracle/src/raw_json_scanner.cpp @@ -1,6 +1,5 @@ #include "raw_json_scanner.h" -#include #include namespace @@ -12,46 +11,6 @@ namespace // reason and reports DeepNesting). constexpr int kMaxNestingDepth = 1024; -/** - * @brief Throttled byte-position reporting for scanSetRanges(). - * - * Threaded through the skip walk so progress can be reported without materializing - * the whole document. Reports are rate-limited so a GUI showing progress isn't - * flooded with interrupts: a callback is invoked at most ~100 times per scan - * regardless of element count. The closing stretch (the last ~1%) is reported - * more finely so a large document doesn't stall the progress bar on the final - * percent before the scan wraps up. - */ -struct ScanProgress -{ - const char *begin = nullptr; - qsizetype size = 0; - RawJson::ScanProgressCallback callback; - qsizetype step = 1; - qsizetype lastReported = 0; - - /** - * @brief Reports the scanner's absolute offset, unless within @p step bytes - * of the previous report and not yet at the end of the document. - */ - void report(const char *p) - { - if (!callback) { - return; - } - const qsizetype offset = p - begin; - if (offset == lastReported) { - return; // the final element often already sits exactly at the end - } - const qsizetype reportingStep = offset >= size - step ? std::max(1, step / 16) : step; - if (offset - lastReported < reportingStep && offset < size) { - return; - } - lastReported = offset; - callback(offset, size); - } -}; - inline bool isWhitespace(char c) { return c == ' ' || c == '\t' || c == '\r' || c == '\n'; @@ -337,9 +296,9 @@ bool skipNumber(const char *&p, const char *end) return true; } -bool skipValue(const char *&p, const char *end, int depth, ScanProgress &scan); -bool skipObject(const char *&p, const char *end, int depth, ScanProgress &scan); -bool skipArray(const char *&p, const char *end, int depth, ScanProgress &scan); +bool skipValue(const char *&p, const char *end, int depth); +bool skipObject(const char *&p, const char *end, int depth); +bool skipArray(const char *&p, const char *end, int depth); bool skipPrimitive(const char *&p, const char *end) { @@ -365,7 +324,7 @@ bool skipPrimitive(const char *&p, const char *end) return false; } -bool skipObject(const char *&p, const char *end, int depth, ScanProgress &scan) +bool skipObject(const char *&p, const char *end, int depth) { if (depth <= 0) { return false; // nest deeper than the cap @@ -389,10 +348,9 @@ bool skipObject(const char *&p, const char *end, int depth, ScanProgress &scan) return false; } ++p; - if (!skipValue(p, end, depth - 1, scan)) { + if (!skipValue(p, end, depth - 1)) { return false; } - scan.report(p); p = skipWhitespace(p, end); if (p >= end) { return false; @@ -409,7 +367,7 @@ bool skipObject(const char *&p, const char *end, int depth, ScanProgress &scan) } } -bool skipArray(const char *&p, const char *end, int depth, ScanProgress &scan) +bool skipArray(const char *&p, const char *end, int depth) { if (depth <= 0) { return false; // nest deeper than the cap @@ -421,10 +379,9 @@ bool skipArray(const char *&p, const char *end, int depth, ScanProgress &scan) return true; } for (;;) { - if (!skipValue(p, end, depth - 1, scan)) { + if (!skipValue(p, end, depth - 1)) { return false; } - scan.report(p); p = skipWhitespace(p, end); if (p >= end) { return false; @@ -441,7 +398,7 @@ bool skipArray(const char *&p, const char *end, int depth, ScanProgress &scan) } } -bool skipValue(const char *&p, const char *end, int depth, ScanProgress &scan) +bool skipValue(const char *&p, const char *end, int depth) { p = skipWhitespace(p, end); if (p >= end) { @@ -450,10 +407,10 @@ bool skipValue(const char *&p, const char *end, int depth, ScanProgress &scan) const char c = *p; if (c == '{') { // pass depth through: skipObject consumes the single decrement for this level - return skipObject(p, end, depth, scan); + return skipObject(p, end, depth); } if (c == '[') { - return skipArray(p, end, depth, scan); + return skipArray(p, end, depth); } // a primitive is a leaf, so it never wastes a nesting level return skipPrimitive(p, end); @@ -465,8 +422,7 @@ bool skipValue(const char *&p, const char *end, int depth, ScanProgress &scan) * For each member invokes @p memberCallback with the key and the byte range of * its value. Advancing @p p is unaffected by the callback. */ -template -bool forEachObjectMember(const char *&p, const char *end, int depth, F &&memberCallback, ScanProgress &scan) +template bool forEachObjectMember(const char *&p, const char *end, int depth, F &&memberCallback) { if (depth <= 0) { return false; // nest deeper than the cap @@ -493,7 +449,7 @@ bool forEachObjectMember(const char *&p, const char *end, int depth, F &&memberC ++p; const char *valueStart = skipWhitespace(p, end); const char *valueEnd = valueStart; - if (!skipValue(valueEnd, end, depth - 1, scan)) { + if (!skipValue(valueEnd, end, depth - 1)) { return false; } if (!memberCallback(key, valueStart, valueEnd)) { @@ -517,7 +473,7 @@ bool forEachObjectMember(const char *&p, const char *end, int depth, F &&memberC } // Counts the direct elements of an array value; returns -1 if the array is malformed. -int countArrayElements(const char *p, const char *end, int depth, ScanProgress &scan) +int countArrayElements(const char *p, const char *end, int depth) { if (depth <= 0) { return -1; // nest deeper than the cap @@ -529,10 +485,9 @@ int countArrayElements(const char *p, const char *end, int depth, ScanProgress & return 0; } for (;;) { - if (!skipValue(p, end, depth - 1, scan)) { + if (!skipValue(p, end, depth - 1)) { return -1; } - scan.report(p); ++count; p = skipWhitespace(p, end); if (p >= end) { @@ -554,7 +509,7 @@ int countArrayElements(const char *p, const char *end, int depth, ScanProgress & namespace RawJson { -QList scanSetRanges(const QByteArray &json, ScanError *error, const ScanProgressCallback &progress) +QList scanSetRanges(const QByteArray &json, ScanError *error) { QList ranges; if (error) { @@ -574,14 +529,6 @@ QList scanSetRanges(const QByteArray &json, ScanError *error, const Sc return fail(QStringLiteral("empty JSON document")); } - // Throttle reports to ~100 per scan so a GUI thread unthrottling them never - // drowns under per-card interrupts, whatever the document size. - ScanProgress scan; - scan.begin = begin; - scan.size = end - begin; - scan.step = std::max(1, scan.size / 100); - scan.callback = progress; - const char *p = skipWhitespace(begin, end); if (p >= end || *p != '{') { return fail(QStringLiteral("top-level JSON must be an object")); @@ -598,57 +545,55 @@ QList scanSetRanges(const QByteArray &json, ScanError *error, const Sc return false; } const char *setP = valueStart; - const bool ok = forEachObjectMember( - setP, valueEnd, kMaxNestingDepth - 1, - [&](const QString &setCode, const char *setStart, const char *setEnd) { - if (setStart >= setEnd || *setStart != '{') { - malformedSetData = true; - return false; - } - SetRange range; - range.dataRange.start = setStart - begin; - range.dataRange.length = setEnd - setStart; - range.code = setCode; + const bool ok = forEachObjectMember(setP, valueEnd, kMaxNestingDepth - 1, + [&](const QString &setCode, const char *setStart, const char *setEnd) { + if (setStart >= setEnd || *setStart != '{') { + malformedSetData = true; + return false; + } + SetRange range; + range.dataRange.start = setStart - begin; + range.dataRange.length = setEnd - setStart; + range.code = setCode; - const char *memberP = setStart; - const bool metaOk = forEachObjectMember( - memberP, setEnd, kMaxNestingDepth - 2, - [&](const QString &field, const char *fs, const char *fe) { - if (field == QStringLiteral("code")) { - return decodeStringMember(fs, fe, range.code); - } - if (field == QStringLiteral("name")) { - return decodeStringMember(fs, fe, range.name); - } - if (field == QStringLiteral("type")) { - return decodeStringMember(fs, fe, range.type); - } - if (field == QStringLiteral("releaseDate")) { - return decodeStringMember(fs, fe, range.releaseDate); - } - if (field == QStringLiteral("cards")) { - if (fs >= fe) { - return false; - } - if (*fs != '[') { - // e.g. "cards": null — treat as an empty array, - // matching Qt's tolerance. - return true; - } - range.dataRange.cardCount = countArrayElements(fs, fe, kMaxNestingDepth - 2, scan); - return range.dataRange.cardCount >= 0; - } - return true; - }, - scan); - if (!metaOk) { - malformedSetData = true; - return false; - } - ranges.append(range); - return true; - }, - scan); + const char *memberP = setStart; + const bool metaOk = forEachObjectMember( + memberP, setEnd, kMaxNestingDepth - 2, + [&](const QString &field, const char *fs, const char *fe) { + if (field == QStringLiteral("code")) { + return decodeStringMember(fs, fe, range.code); + } + if (field == QStringLiteral("name")) { + return decodeStringMember(fs, fe, range.name); + } + if (field == QStringLiteral("type")) { + return decodeStringMember(fs, fe, range.type); + } + if (field == QStringLiteral("releaseDate")) { + return decodeStringMember(fs, fe, range.releaseDate); + } + if (field == QStringLiteral("cards")) { + if (fs >= fe) { + return false; + } + if (*fs != '[') { + // e.g. "cards": null — treat as an empty array, + // matching Qt's tolerance. + return true; + } + range.dataRange.cardCount = + countArrayElements(fs, fe, kMaxNestingDepth - 2); + return range.dataRange.cardCount >= 0; + } + return true; + }); + if (!metaOk) { + malformedSetData = true; + return false; + } + ranges.append(range); + return true; + }); if (!ok) { malformedSetData = true; return false; @@ -657,7 +602,7 @@ QList scanSetRanges(const QByteArray &json, ScanError *error, const Sc return true; }; - if (!forEachObjectMember(p, end, kMaxNestingDepth, topLevelCallback, scan)) { + if (!forEachObjectMember(p, end, kMaxNestingDepth, topLevelCallback)) { return fail(malformedSetData ? QStringLiteral("malformed set data") : QStringLiteral("malformed JSON")); } p = skipWhitespace(p, end); @@ -670,7 +615,6 @@ QList scanSetRanges(const QByteArray &json, ScanError *error, const Sc if (ranges.isEmpty()) { return fail(QStringLiteral("no sets found in \"data\"")); } - scan.report(end); return ranges; } diff --git a/oracle/src/raw_json_scanner.h b/oracle/src/raw_json_scanner.h index 04fcc52e4..f6e3a4647 100644 --- a/oracle/src/raw_json_scanner.h +++ b/oracle/src/raw_json_scanner.h @@ -4,7 +4,6 @@ #include #include #include -#include namespace RawJson { @@ -43,18 +42,11 @@ struct ScanError QString message; }; -/** - * @brief Optional progress callback receiving @c (bytesRead, totalBytes) while - * the document is walked. Invoked from the scanning thread; the caller decides - * how the throttled offsets are relayed to a GUI event loop. - */ -using ScanProgressCallback = std::function; - /** * @brief Scans a full MTGJSON document without materializing the JSON tree. * * Splits the top-level "data" object into per-set byte ranges and reads each - * set's metadata directly from the raw bytes. The Oracle importer can then + * set's metadata directly from the raw bytes. The oracle importer can then * parse one set at a time during import, keeping peak memory far below a single * QJsonDocument::fromJson() over the whole file. * @@ -75,14 +67,9 @@ using ScanProgressCallback = std::function scanSetRanges(const QByteArray &json, - ScanError *error = nullptr, - const ScanProgressCallback &progress = ScanProgressCallback()); +QList scanSetRanges(const QByteArray &json, ScanError *error = nullptr); } // namespace RawJson diff --git a/servatrice/CMakeLists.txt b/servatrice/CMakeLists.txt index 68e422d8c..5d8089ad1 100644 --- a/servatrice/CMakeLists.txt +++ b/servatrice/CMakeLists.txt @@ -6,9 +6,7 @@ project(Servatrice VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${ set(servatrice_SOURCES src/email_parser.cpp - src/event_loop_watchdog.cpp src/main.cpp - src/metrics_registry.cpp src/servatrice.cpp src/servatrice_connection_pool.cpp src/servatrice_database_interface.cpp diff --git a/servatrice/migrations/servatrice_0036_to_0037.sql b/servatrice/migrations/servatrice_0036_to_0037.sql index 576ab53b4..3abc14fed 100644 --- a/servatrice/migrations/servatrice_0036_to_0037.sql +++ b/servatrice/migrations/servatrice_0036_to_0037.sql @@ -11,7 +11,7 @@ -- anyone who knows the (unguessable) token, until the share expires. CREATE TABLE IF NOT EXISTS `cockatrice_deck_share` ( `id` int(7) unsigned zerofill NOT NULL auto_increment, - `token` varchar(64) COLLATE utf8mb4_bin NOT NULL, + `token` varchar(64) NOT NULL, `name` varchar(64) NOT NULL, `created_by` int(7) unsigned NULL, `created_at` datetime NOT NULL, @@ -53,18 +53,18 @@ ALTER TABLE `cockatrice_decklist_folders` -- 3. Per-deck preview metadata so clients can render another user's public -- decks (e.g. in a visual deck storage grid) without downloading each deck --- list. The metadata is derived by the server from the deck content (the color --- identity is supplied by the uploading client); decks uploaded before this --- migration have empty values until they are re-uploaded. +-- list. The metadata is computed by the uploading client; decks uploaded +-- before this migration have empty values until they are re-uploaded. ALTER TABLE `cockatrice_decklist_files` - ADD COLUMN `banner_card_name` varchar(255) NULL AFTER `is_public`, + ADD COLUMN `banner_card_name` varchar(255) NULL AFTER `content`, ADD COLUMN `banner_card_provider` varchar(32) NULL AFTER `banner_card_name`, ADD COLUMN `color_identity` varchar(5) NULL AFTER `banner_card_provider`; --- 4. Per-deck tags for public decks. The server renders the deck's own tags --- into a JSON array, so another user's public decks can filter by tag without --- downloading each deck list. Decks uploaded before this migration have NULL --- tags until they are re-uploaded. +-- 4. Per-deck tags for public decks. The uploading client sends a +-- comma-separated tag string (matching the deck's own tags), so another user's +-- public decks can render and filter by tag without downloading each deck list. +-- Decks uploaded before this migration have NULL tags until they are +-- re-uploaded. ALTER TABLE `cockatrice_decklist_files` ADD COLUMN `tags` text NULL AFTER `color_identity`; diff --git a/servatrice/servatrice.ini.example b/servatrice/servatrice.ini.example index fc0ba11f2..3e94e5790 100644 --- a/servatrice/servatrice.ini.example +++ b/servatrice/servatrice.ini.example @@ -382,19 +382,6 @@ max_reports_per_day=10 ; Maximum number of report comments a single user can post per hour; default is 30; set to 0 to disable the limit max_comments_per_hour=30 -[metrics] -; Command containers that take longer than this many milliseconds are logged -; as slow commands. Set to 0 to disable the log line. A latency spike produces -; one warning per slow container with no rate limiting of its own -- a bad -; patch can briefly flood the log, which is how you notice it. -slow_command_ms=500 - -; Each socket pool thread runs a watchdog heartbeat. If a heartbeat arrives -; this many milliseconds late, the stall is logged and exposed as -; servatrice_eventloop_* metrics in the Developer tab. Set to 0 to disable -; the watchdogs. -stall_warn_ms=2000 - [logging] ; Admin/Moderators can query the stored logs for information when looking up reports by various players. This ; option can allow or disallow them from doing so. @@ -468,8 +455,3 @@ cleanup_interval=60 ; Maximum number of decks a single share link can contain. ; Default: 50 max_decks_per_share=50 - -; Maximum number of share links a single user may create per day. -; A value of 0 disables the limit. -; Default: 0 -max_shares_per_day=0 diff --git a/servatrice/servatrice.sql b/servatrice/servatrice.sql index 4fbc1d8bd..8baabf3bd 100644 --- a/servatrice/servatrice.sql +++ b/servatrice/servatrice.sql @@ -25,9 +25,6 @@ INSERT INTO cockatrice_schema_version VALUES(37); -- users and user data tables CREATE TABLE IF NOT EXISTS `cockatrice_users` ( `id` int(7) unsigned zerofill NOT NULL auto_increment, - -- Bitfield of staff levels: 1 = admin (implies moderator), 2 = moderator, - -- 4 = judge, 8 = developer. Operators set these by hand with - -- "UPDATE cockatrice_users SET admin = ...". `admin` tinyint(1) NOT NULL, `name` varchar(35) NOT NULL, `realname` varchar(255) NOT NULL, @@ -80,7 +77,7 @@ CREATE TABLE IF NOT EXISTS `cockatrice_decklist_files` ( -- anyone who knows the (unguessable) token, until the share expires. CREATE TABLE IF NOT EXISTS `cockatrice_deck_share` ( `id` int(7) unsigned zerofill NOT NULL auto_increment, - `token` varchar(64) COLLATE utf8mb4_bin NOT NULL, + `token` varchar(64) NOT NULL, `name` varchar(64) NOT NULL, `created_by` int(7) unsigned NULL, `created_at` datetime NOT NULL, diff --git a/servatrice/src/deck_tag_serialization.h b/servatrice/src/deck_tag_serialization.h deleted file mode 100644 index 162b0604d..000000000 --- a/servatrice/src/deck_tag_serialization.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef DECK_TAG_SERIALIZATION_H -#define DECK_TAG_SERIALIZATION_H - -#include -#include -#include -#include -#include - -/** - * @brief Encodes deck tags as a compact JSON array for storage in a text column. - * - * Deck tags are stored as JSON (rather than a delimited string) so tag names may - * contain any character, and decoded uniformly everywhere they are read. - */ -inline QString serializeDeckTags(const QStringList &tags) -{ - QJsonArray array; - for (const QString &tag : tags) { - array.append(tag); - } - return QString::fromUtf8(QJsonDocument(array).toJson(QJsonDocument::Compact)); -} - -/** @brief Decodes deck tags previously written by serializeDeckTags. */ -inline QStringList deserializeDeckTags(const QString &serialized) -{ - QStringList tags; - if (serialized.isEmpty()) { - return tags; - } - const QJsonArray array = QJsonDocument::fromJson(serialized.toUtf8()).array(); - for (const QJsonValue &tag : array) { - tags.append(tag.toString()); - } - return tags; -} - -#endif // DECK_TAG_SERIALIZATION_H diff --git a/servatrice/src/event_loop_watchdog.cpp b/servatrice/src/event_loop_watchdog.cpp deleted file mode 100644 index e50bd4201..000000000 --- a/servatrice/src/event_loop_watchdog.cpp +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @file event_loop_watchdog.cpp - * @ingroup Servatrice - */ - -#include "event_loop_watchdog.h" - -#include "servatrice.h" - -#include - -EventLoopWatchdog::EventLoopWatchdog(Servatrice *_servatrice, QString _threadName) - : QObject(nullptr), servatrice(_servatrice), threadName(std::move(_threadName)) -{ -} - -void EventLoopWatchdog::start() -{ - heartbeatTimer = new QTimer(this); - sinceLastTick.start(); - connect(heartbeatTimer, &QTimer::timeout, this, &EventLoopWatchdog::checkHeartbeat); - heartbeatTimer->start(HeartbeatIntervalMs); -} - -void EventLoopWatchdog::checkHeartbeat() -{ - const qint64 elapsedMs = sinceLastTick.restart(); - const qint64 overshootMs = qMax(0, elapsedMs - HeartbeatIntervalMs); - if (overshootMs < servatrice->getMetricsStallWarnMs()) { - return; - } - - servatrice->observeEventLoopStall(threadName, overshootMs); -} diff --git a/servatrice/src/event_loop_watchdog.h b/servatrice/src/event_loop_watchdog.h deleted file mode 100644 index b9061ff97..000000000 --- a/servatrice/src/event_loop_watchdog.h +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @file event_loop_watchdog.h - * @ingroup Servatrice - */ - -#ifndef EVENT_LOOP_WATCHDOG_H -#define EVENT_LOOP_WATCHDOG_H - -#include -#include -#include - -class Servatrice; -class QTimer; - -/** - * @brief Detects blocked or overloaded worker event loops. - * - * One instance lives in each socket pool thread. A heartbeat timer tick that - * arrives late means the loop spent that time elsewhere: busy work, a queued - * slot, or a hard wedge. Overshoots past the configured threshold bump - * lock-free counters on the metrics registry and log one warning per stall, - * so a stuck pool thread becomes visible instead of silent lag. - */ -class EventLoopWatchdog : public QObject -{ - Q_OBJECT -public: - /// How often the heartbeat expects to fire. Small enough to catch short stalls. - static constexpr int HeartbeatIntervalMs = 500; - - EventLoopWatchdog(Servatrice *_servatrice, QString _threadName); - - /** - * Starts the heartbeat timer. Must be invoked queued after the instance - * was moved to its target thread so the timer lives there too. - */ - void start(); - -private slots: - void checkHeartbeat(); - -private: - Servatrice *servatrice; - QString threadName; - QElapsedTimer sinceLastTick; - QTimer *heartbeatTimer = nullptr; -}; - -#endif diff --git a/servatrice/src/metrics_registry.cpp b/servatrice/src/metrics_registry.cpp deleted file mode 100644 index 99ce390f1..000000000 --- a/servatrice/src/metrics_registry.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "metrics_registry.h" - -#include - -void MetricsRegistry::observeCommand(int typeId, qint64 elapsedMs) -{ - if (typeId < 0 || typeId >= MaxTypes) { - typeId = MaxTypes - 1; // overflow slot keeps misrouted ids visible - } - if (elapsedMs < 0) { - elapsedMs = 0; - } - - TypeStats &stats = slotFor(typeId); - stats.count.fetch_add(1, std::memory_order_relaxed); - stats.totalMs.fetch_add(elapsedMs, std::memory_order_relaxed); - totalCommandsCounter.fetch_add(1, std::memory_order_relaxed); - totalTimeCounter.fetch_add(elapsedMs, std::memory_order_relaxed); -} - -void MetricsRegistry::observeGameStartDurationMs(qint64 elapsedMs) -{ - if (elapsedMs < 0) { - elapsedMs = 0; - } - - gameStartStats.count.fetch_add(1, std::memory_order_relaxed); - gameStartStats.totalMs.fetch_add(elapsedMs, std::memory_order_relaxed); -} - -MetricsRegistry::TypeStats &MetricsRegistry::slotFor(int typeId) -{ - return typeSlots[static_cast(typeId)]; -} - -const MetricsRegistry::TypeStats &MetricsRegistry::slotFor(int typeId) const -{ - return typeSlots[static_cast(typeId)]; -} - -int MetricsRegistry::activeTypeCount() const -{ - int active = 0; - for (int type = 0; type < MaxTypes; ++type) { - if (slotFor(type).count.load(std::memory_order_relaxed) > 0) { - ++active; - } - } - return active; -} - -QList MetricsRegistry::collectActiveStats() const -{ - QList result; - for (int type = 0; type < MaxTypes; ++type) { - const TypeStats &stats = slotFor(type); - const qint64 count = stats.count.load(std::memory_order_relaxed); - if (count == 0) { - continue; - } - result.append({type, count, stats.totalMs.load(std::memory_order_relaxed)}); - } - return result; -} - -MetricsRegistry::GameStartSnapshot MetricsRegistry::getGameStartSnapshot() const -{ - return {gameStartStats.count.load(std::memory_order_relaxed), - gameStartStats.totalMs.load(std::memory_order_relaxed)}; -} \ No newline at end of file diff --git a/servatrice/src/metrics_registry.h b/servatrice/src/metrics_registry.h deleted file mode 100644 index 4df1057c2..000000000 --- a/servatrice/src/metrics_registry.h +++ /dev/null @@ -1,120 +0,0 @@ -/** - * @file metrics_registry.h - * @ingroup Servatrice - */ - -#ifndef METRICS_REGISTRY_H -#define METRICS_REGISTRY_H - -#include -#include -#include -#include - -/** - * @brief Lock-free accumulation of command processing statistics. - * - * observeCommand() is called once per processed command from whichever socket - * thread handled it. It uses relaxed atomic adds on preallocated storage only, - * so it introduces no locks, allocations, or shared cache-line ping-pong - * beyond the unavoidable counter updates. - * - * Reading happens rarely (metrics scraping), accepts momentary tears between - * related counters, and therefore also needs no synchronization. - * - * Only counts and totals are retained. An earlier Prometheus-style cumulative - * histogram (per-type, time-bucketed) was cut because nothing in the server - * ever wrote it out; it belongs to the future /metrics exporter that needs it. - */ -class MetricsRegistry -{ -public: - /** - * Extension numbers are only unique per command kind, so recorded ids - * combine the kind index with the protobuf extension number. - * - * The stride is only as wide as it needs to be: 1280 is the first round - * number above the largest extension actually in use (ModeratorCommand = - * 1206) and keeps the preallocated TypeStats array small. Bump it if a new - * command exceeds it. - */ - static constexpr int KindStride = 1280; - - static constexpr int NumKinds = 6; - - static constexpr const char *KindNames[NumKinds] = {"session", "room", "game", "moderator", "admin", "developer"}; - - /// Upper bound on distinct command type ids (see typeIdFor). - static constexpr int MaxTypes = NumKinds * KindStride; - - /// Guard against typeIdFor() overflowing into the neighbouring kind's slots. - static_assert(KindStride > 1206, "KindStride must exceed the highest command extension number in use"); - - static int typeIdFor(int kindIndex, int extensionNumber) - { - return kindIndex * KindStride + extensionNumber; - } - - void observeCommand(int typeId, qint64 elapsedMs); - - /** - * Records how long one game start took to bring every player's zones - * online. Kept separate from command timings because it is triggered by - * the server itself and can dwarf any single command when decks are huge. - */ - void observeGameStartDurationMs(qint64 elapsedMs); - - /// Total number of observed commands across all types. - qint64 totalCommands() const - { - return totalCommandsCounter.load(std::memory_order_relaxed); - } - - /// Cumulative processing milliseconds across all types. - qint64 totalTimeMs() const - { - return totalTimeCounter.load(std::memory_order_relaxed); - } - - /// Number of distinct type slots that have seen at least one sample. - int activeTypeCount() const; - - struct ActiveTypeStats - { - int typeId; - qint64 count; - qint64 totalMs; - }; - - /** - * Returns stats for every type slot that has seen at least one sample. - * Callers resolve the numeric type id to a human-readable label via - * typeIdFor()/KindNames as needed. - */ - QList collectActiveStats() const; - - struct GameStartSnapshot - { - qint64 count; - qint64 totalMs; - }; - - GameStartSnapshot getGameStartSnapshot() const; - -private: - struct TypeStats - { - std::atomic count{0}; - std::atomic totalMs{0}; - }; - - TypeStats &slotFor(int typeId); - const TypeStats &slotFor(int typeId) const; - - std::array typeSlots{}; - TypeStats gameStartStats{}; - std::atomic totalCommandsCounter{0}; - std::atomic totalTimeCounter{0}; -}; - -#endif \ No newline at end of file diff --git a/servatrice/src/servatrice.cpp b/servatrice/src/servatrice.cpp index 56aeedb9a..0c1d6f18c 100644 --- a/servatrice/src/servatrice.cpp +++ b/servatrice/src/servatrice.cpp @@ -20,7 +20,6 @@ #include "servatrice.h" #include "email_parser.h" -#include "event_loop_watchdog.h" #include "isl_interface.h" #include "main.h" #include "servatrice_connection_pool.h" @@ -39,7 +38,6 @@ #include #include #include -#include #include #include #include @@ -65,7 +63,6 @@ Servatrice_GameServer::Servatrice_GameServer(Servatrice *_server, server->addDatabaseInterface(newThread, newDatabaseInterface); newThread->start(); - server->watchWorkerThread(newThread); QMetaObject::invokeMethod(newDatabaseInterface, "initDatabase", Qt::BlockingQueuedConnection, Q_ARG(QSqlDatabase, _sqlDatabase)); @@ -89,6 +86,7 @@ void Servatrice_GameServer::incomingConnection(qintptr socketDescriptor) Servatrice_ConnectionPool *pool = findLeastUsedConnectionPool(); auto ssi = new TcpServerSocketInterface(server, pool->getDatabaseInterface()); + connect(ssi, SIGNAL(incTxBytes(qint64)), this, SLOT(incTxBytes(qint64))); ssi->moveToThread(pool->thread()); pool->addClient(); connect(ssi, SIGNAL(destroyed()), pool, SLOT(removeClient())); @@ -133,7 +131,6 @@ Servatrice_WebsocketGameServer::Servatrice_WebsocketGameServer(Servatrice *_serv server->addDatabaseInterface(newThread, newDatabaseInterface); newThread->start(); - server->watchWorkerThread(newThread); QMetaObject::invokeMethod(newDatabaseInterface, "initDatabase", Qt::BlockingQueuedConnection, Q_ARG(QSqlDatabase, _sqlDatabase)); @@ -159,6 +156,7 @@ void Servatrice_WebsocketGameServer::onNewConnection() Servatrice_ConnectionPool *pool = findLeastUsedConnectionPool(); auto ssi = new WebsocketServerSocketInterface(server, pool->getDatabaseInterface()); + connect(ssi, SIGNAL(incTxBytes(quint64)), this, SLOT(incTxBytes(quint64))); /* * Due to a Qt limitation, websockets can't be moved to another thread. * This will hopefully change in Qt6 if QtWebSocket will be integrated in QtNetwork @@ -228,13 +226,6 @@ bool Servatrice::initServer() { serverId = getServerID(); - - // METRICS (always active. Slow-command logging and stall watchdogs are - // controlled by their respective thresholds below). Read up front so the - // values are available before any pool thread is started and watchdogged. - metricsSlowCommandMs = settingsCache->value("metrics/slow_command_ms", 500).toInt(); - metricsStallWarnMs = qMax(0, settingsCache->value("metrics/stall_warn_ms", 2000).toInt()); - if (getAuthenticationMethodString() == "sql") { qDebug() << "Authenticating method: sql"; authenticationMethod = AuthenticationSql; @@ -487,55 +478,9 @@ bool Servatrice::initServer() } setRequiredFeatures(getRequiredFeatures()); - return true; } -void Servatrice::observeGameStartDurationMs(qint64 elapsedMs) -{ - metricsRegistry.observeGameStartDurationMs(elapsedMs); -} - -void Servatrice::observeEventLoopStall(const QString &threadName, qint64 overshootMs) -{ - eventLoopStallsTotal.fetch_add(1, std::memory_order_relaxed); - eventLoopLastStallMs.store(overshootMs, std::memory_order_relaxed); - qint64 prevMax = eventLoopMaxStallMs.load(std::memory_order_relaxed); - while (overshootMs > prevMax && - !eventLoopMaxStallMs.compare_exchange_weak(prevMax, overshootMs, std::memory_order_relaxed)) { - // retry until the max is at least as high as the new sample - } - - qWarning() << "Event loop stall in" << threadName << "- heartbeat overshot by" << overshootMs << "ms"; -} - -void Servatrice::watchWorkerThread(QThread *thread) -{ - if (metricsStallWarnMs <= 0) { - return; // watchdogs disabled via metrics/stall_warn_ms = 0 - } - - auto *watchdog = new EventLoopWatchdog(this, thread->objectName()); - connect(thread, &QThread::finished, watchdog, &QObject::deleteLater); - watchdog->moveToThread(thread); - QMetaObject::invokeMethod(watchdog, &EventLoopWatchdog::start, Qt::QueuedConnection); -} - -qint64 Servatrice::getCardsInGamesTotal() const -{ - qint64 total = 0; - QReadLocker roomsLocker(&roomsLock); // locking order: roomsLock before gamesLock/gameMutex - QMapIterator roomIterator(rooms); - while (roomIterator.hasNext()) { - Server_Room *room = roomIterator.next().value(); - QReadLocker gamesLocker(&room->gamesLock); - for (auto *game : room->getGames()) { - total += game->getCardsInGame(); - } - } - return total; -} - void Servatrice::addDatabaseInterface(QThread *thread, Servatrice_DatabaseInterface *databaseInterface) { databaseInterfaces.insert(thread, databaseInterface); @@ -1096,12 +1041,6 @@ int Servatrice::getDeckShareMaxDecksPerShare() const return settingsCache->value("deck_share/max_decks_per_share", 50).toInt(); } -int Servatrice::getDeckShareMaxSharesPerDay() const -{ - // default: no limit - return settingsCache->value("deck_share/max_shares_per_day", 0).toInt(); -} - int Servatrice::getNumberOfTCPPools() const { return settingsCache->value("server/number_pools", 1).toInt(); diff --git a/servatrice/src/servatrice.h b/servatrice/src/servatrice.h index f39a44d51..f4b4146a4 100644 --- a/servatrice/src/servatrice.h +++ b/servatrice/src/servatrice.h @@ -20,8 +20,6 @@ #ifndef SERVATRICE_H #define SERVATRICE_H -#include "metrics_registry.h" - #include #include #include @@ -32,7 +30,6 @@ #include #include #include -#include #include #include #include @@ -175,12 +172,6 @@ private: int uptime; QMutex txBytesMutex, rxBytesMutex; quint64 txBytes, rxBytes; - MetricsRegistry metricsRegistry; - int metricsSlowCommandMs = 500; - int metricsStallWarnMs = 2000; - std::atomic eventLoopStallsTotal{0}; ///< heartbeat overshoots past the warn threshold - std::atomic eventLoopLastStallMs{0}; ///< overshoot of the most recent stall - std::atomic eventLoopMaxStallMs{0}; ///< worst overshoot seen since process start QString shutdownReason; int shutdownMinutes; @@ -281,7 +272,6 @@ public: int getDeckShareExpiryDays() const; int getDeckShareCleanupInterval() const; int getDeckShareMaxDecksPerShare() const; - int getDeckShareMaxSharesPerDay() const; int getMaxUsersPerAddress() const; int getMessageCountingInterval() const override; int getMaxMessageCountPerInterval() const override; @@ -301,49 +291,6 @@ public: void incRxBytes(quint64 num); void addDatabaseInterface(QThread *thread, Servatrice_DatabaseInterface *databaseInterface); - // Metrics (see [metrics] section in servatrice.ini.example) - MetricsRegistry &getMetricsRegistry() - { - return metricsRegistry; - } - /** - * Sums cards across all zones of all running games. Each game takes its - * own gameMutex -- the hot per-game lock every game action contends on -- - * and then iterates every player's zones, so the scrape cost is really - * O(total cards in play) plus one mutex acquisition per live game. Keep - * scrapes infrequent in big multiplayer rooms. - */ - qint64 getCardsInGamesTotal() const; - int getMetricsSlowCommandMs() const - { - return metricsSlowCommandMs; - } - /// Heartbeat overshoot that counts as a stall. A value of 0 disables the watchdogs. - int getMetricsStallWarnMs() const - { - return metricsStallWarnMs; - } - void observeGameStartDurationMs(qint64 elapsedMs) override; - qint64 getEventLoopStallsTotal() const - { - return eventLoopStallsTotal.load(std::memory_order_relaxed); - } - qint64 getEventLoopLastStallMs() const - { - return eventLoopLastStallMs.load(std::memory_order_relaxed); - } - qint64 getEventLoopMaxStallMs() const - { - return eventLoopMaxStallMs.load(std::memory_order_relaxed); - } - /// Records one heartbeat overshoot and logs a single warning for it. - void observeEventLoopStall(const QString &threadName, qint64 overshootMs); - /** - * Installs an EventLoopWatchdog in @p thread. Called once per socket pool - * thread right after it starts. - */ - void watchWorkerThread(QThread *thread); - bool islConnectionExists(int _serverId) const; void addIslInterface(int _serverId, IslInterface *interface); void removeIslInterface(int _serverId); diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp index 7a1456b2b..28c7ff2aa 100644 --- a/servatrice/src/servatrice_database_interface.cpp +++ b/servatrice/src/servatrice_database_interface.cpp @@ -1,6 +1,5 @@ #include "servatrice_database_interface.h" -#include "deck_tag_serialization.h" #include "servatrice.h" #include "serversocketinterface.h" #include "settingscache.h" @@ -14,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -89,10 +87,9 @@ bool Servatrice_DatabaseInterface::openDatabase() << dbversion << "to version" << expectedversion; return false; } else if (dbversion > expectedversion) { - qCCritical(DatabaseInterfaceLog) - << poolStr << "Error opening database: the database schema version" << dbversion - << "is too new, you need to update Servatrice" << "(Currently running Servatrice actually uses version" - << expectedversion << ")"; + qCCritical(DatabaseInterfaceLog) << poolStr << "Error opening database: the database schema version" + << dbversion << "is too new, you need to update servatrice" + << "(this servatrice actually uses version" << expectedversion << ")"; return false; } } else { @@ -102,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()) { @@ -692,10 +642,6 @@ ServerInfo_User Servatrice_DatabaseInterface::evalUserQueryResult(const QSqlQuer userLevel |= ServerInfo_User::IsJudge; } - if (is_admin & 8) { - userLevel |= ServerInfo_User::IsDeveloper; - } - result.set_user_level(userLevel); const QString country = query->value(3).toString(); @@ -1085,8 +1031,7 @@ bool Servatrice_DatabaseInterface::createDeckShare(const QString &token, const QString &name, int userId, const QList &items, - int expiryDays, - qint64 &expiresAt) + int expiryDays) { checkSql(); @@ -1094,9 +1039,7 @@ bool Servatrice_DatabaseInterface::createDeckShare(const QString &token, return false; } - if (!sqlDatabase.transaction()) { - return false; - } + sqlDatabase.transaction(); QSqlQuery *query = prepareQuery("insert into {prefix}_deck_share (token, name, created_by, created_at, expires_at) " "values (:token, :name, :created_by, NOW(), DATE_ADD(NOW(), INTERVAL :days DAY))"); @@ -1105,26 +1048,11 @@ bool Servatrice_DatabaseInterface::createDeckShare(const QString &token, query->bindValue(":created_by", userId < 1 ? QVariant() : userId); query->bindValue(":days", expiryDays); if (!execSqlQuery(query)) { - // A failed execSqlQuery has already closed and reopened the connection, - // which implicitly discards the transaction; rollback below is a no-op. sqlDatabase.rollback(); return false; } const int shareId = query->lastInsertId().toInt(); - - // Read the expiry back from the database so the value returned to the client - // matches the server clock rather than being approximated client-side. - QSqlQuery *expiryQuery = prepareQuery("select UNIX_TIMESTAMP(expires_at) from {prefix}_deck_share where id = :id"); - expiryQuery->bindValue(":id", shareId); - if (!execSqlQuery(expiryQuery) || !expiryQuery->next()) { - // See the note above: after a failed execSqlQuery the transaction is - // already gone because the connection was torn down. - sqlDatabase.rollback(); - return false; - } - expiresAt = expiryQuery->value(0).toLongLong(); - for (int i = 0; i < items.size(); ++i) { const DeckShareItemRecord &item = items.at(i); QSqlQuery *itemQuery = prepareQuery("insert into {prefix}_deck_share_item (share_id, name, tags, banner_card, " @@ -1132,24 +1060,23 @@ bool Servatrice_DatabaseInterface::createDeckShare(const QString &token, ":tags, :banner_card, :game_format, :color_identity, :content, :position)"); itemQuery->bindValue(":share_id", shareId); itemQuery->bindValue(":name", item.name); - itemQuery->bindValue(":tags", serializeDeckTags(item.tags)); + QJsonArray tagArray; + for (const QString &tag : item.tags) { + tagArray.append(tag); + } + itemQuery->bindValue(":tags", QString::fromUtf8(QJsonDocument(tagArray).toJson(QJsonDocument::Compact))); itemQuery->bindValue(":banner_card", item.bannerCard); itemQuery->bindValue(":game_format", item.gameFormat); itemQuery->bindValue(":color_identity", item.colorIdentity); itemQuery->bindValue(":content", item.content); itemQuery->bindValue(":position", i); if (!execSqlQuery(itemQuery)) { - // See the note above: the transaction is already gone after the - // reconnect performed by a failed execSqlQuery. sqlDatabase.rollback(); return false; } } - if (!sqlDatabase.commit()) { - sqlDatabase.rollback(); - return false; - } + sqlDatabase.commit(); return true; } @@ -1183,7 +1110,10 @@ bool Servatrice_DatabaseInterface::getDeckShareList(const QString &token, DeckShareItemRecord item; item.id = itemQuery->value(0).toInt(); item.name = itemQuery->value(1).toString(); - item.tags = deserializeDeckTags(itemQuery->value(2).toString()); + const QJsonArray tagArray = QJsonDocument::fromJson(itemQuery->value(2).toString().toUtf8()).array(); + for (const QJsonValue &tag : tagArray) { + item.tags.append(tag.toString()); + } item.bannerCard = itemQuery->value(3).toString(); item.gameFormat = itemQuery->value(4).toString(); item.colorIdentity = itemQuery->value(5).toString(); @@ -1218,45 +1148,6 @@ void Servatrice_DatabaseInterface::cleanupExpiredDeckShares() execSqlQuery(query); } -bool Servatrice_DatabaseInterface::getDeckSharesForUser(int userId, QList &shares) -{ - checkSql(); - - QSqlQuery *query = prepareQuery("select s.id, s.name, UNIX_TIMESTAMP(s.created_at), " - "UNIX_TIMESTAMP(s.expires_at), count(i.id) from {prefix}_deck_share s left join " - "{prefix}_deck_share_item i on i.share_id = s.id where s.created_by = :created_by " - "group by s.id, s.name, s.created_at, s.expires_at order by s.created_at desc"); - query->bindValue(":created_by", userId); - if (!execSqlQuery(query)) { - return false; - } - - shares.clear(); - while (query->next()) { - DeckShareSummaryRecord summary; - summary.id = query->value(0).toInt(); - summary.name = query->value(1).toString(); - summary.creationTime = query->value(2).toLongLong(); - summary.expiresAt = query->value(3).toLongLong(); - summary.itemCount = query->value(4).toInt(); - shares.append(summary); - } - return true; -} - -bool Servatrice_DatabaseInterface::deleteDeckShare(int shareId, int userId) -{ - checkSql(); - - QSqlQuery *query = prepareQuery("delete from {prefix}_deck_share where id = :id and created_by = :created_by"); - query->bindValue(":id", shareId); - query->bindValue(":created_by", userId); - if (!execSqlQuery(query)) { - return false; - } - return query->numRowsAffected() > 0; -} - void Servatrice_DatabaseInterface::logMessage(const int senderId, const QString &senderName, const QString &senderIp, @@ -1679,7 +1570,7 @@ QList Servatrice_DatabaseInterface::getModeratorLastL QSqlQuery *query = prepareQuery("SELECT u.name, u.admin, UNIX_TIMESTAMP(a.last_login) " "FROM {prefix}_users u " "LEFT JOIN {prefix}_user_analytics a ON a.id = u.id " - "WHERE (u.admin & 15) <> 0 ORDER BY u.name"); + "WHERE (u.admin & 7) <> 0 ORDER BY u.name"); if (!execSqlQuery(query)) { qCWarning(DatabaseInterfaceLog) << "Failed to collect moderator login information: SQL Error"; @@ -1700,9 +1591,6 @@ QList Servatrice_DatabaseInterface::getModeratorLastL if (isAdmin & 4) { userLevel |= ServerInfo_User::IsJudge; } - if (isAdmin & 8) { - userLevel |= ServerInfo_User::IsDeveloper; - } loginDetails.set_user_level(userLevel); if (!query->value(2).isNull()) { @@ -1714,38 +1602,6 @@ QList Servatrice_DatabaseInterface::getModeratorLastL return results; } -Servatrice_DatabaseInterface::UptimeSnapshot Servatrice_DatabaseInterface::getLatestUptimeSnapshot(int serverId) -{ - UptimeSnapshot snapshot; - - if (!checkSql()) { - return snapshot; - } - - QSqlQuery *query = prepareQuery("SELECT users_count, mods_count, games_count, tx_bytes, rx_bytes, uptime, " - "UNIX_TIMESTAMP(timest) FROM {prefix}_uptime " - "WHERE id_server = :id_server ORDER BY timest DESC LIMIT 1"); - query->bindValue(":id_server", serverId); - - if (!execSqlQuery(query)) { - qCWarning(DatabaseInterfaceLog) << "Failed to collect server stats snapshot: SQL Error"; - return snapshot; - } - - if (query->next()) { - snapshot.valid = true; - snapshot.usersCount = query->value(0).toULongLong(); - snapshot.modsCount = query->value(1).toULongLong(); - snapshot.gamesCount = query->value(2).toULongLong(); - snapshot.txBytes = query->value(3).toULongLong(); - snapshot.rxBytes = query->value(4).toULongLong(); - snapshot.uptimeSecs = query->value(5).toULongLong(); - snapshot.timest = query->value(6).toULongLong(); - } - - return snapshot; -} - bool Servatrice_DatabaseInterface::removeUserAvatar(const QString &userName) { if (!checkSql()) { diff --git a/servatrice/src/servatrice_database_interface.h b/servatrice/src/servatrice_database_interface.h index ce51a2474..c33669f0d 100644 --- a/servatrice/src/servatrice_database_interface.h +++ b/servatrice/src/servatrice_database_interface.h @@ -29,16 +29,6 @@ struct DeckShareItemRecord QString content; ///< Deck content (native format); empty in list queries. }; -/** @brief Summary of a share bundle owned by a user. */ -struct DeckShareSummaryRecord -{ - int id = -1; ///< Database id, used for revocation. - QString name; ///< Share name. - qint64 creationTime = 0; ///< Unix timestamp at which the share was created. - qint64 expiresAt = 0; ///< Unix timestamp at which the share expires. - int itemCount = 0; ///< Number of decks in the bundle. -}; - class Servatrice_DatabaseInterface : public Server_DatabaseInterface { Q_OBJECT @@ -54,7 +44,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, @@ -102,24 +91,12 @@ public: const QList &replayList) override; DeckList *getDeckFromDatabase(int deckId, int userId) override; - /** - * @brief Creates a new temporary deck share bundle. - * @param expiresAt Receives the actual expiry read back from the database. - * @return false on failure. - */ + /** @brief Creates a new temporary deck share bundle. Returns false on failure. */ bool createDeckShare(const QString &token, const QString &name, int userId, const QList &items, - int expiryDays, - qint64 &expiresAt); - /** @brief Lists the share bundles created by a user, newest first. */ - bool getDeckSharesForUser(int userId, QList &shares); - /** - * @brief Deletes one of a user's own share bundles (cascades to its items). - * @return false if no such bundle belongs to the user. - */ - bool deleteDeckShare(int shareId, int userId); + int expiryDays); /** * @brief Looks up a valid (non-expired) share bundle by token. * @return false if the token is unknown or expired. @@ -194,21 +171,6 @@ public: QList getUserSessions(const QString &userName, int limit); QList getUserAlts(const QString &userName); QList getModeratorLastLogins(); - - // Uptime snapshot as recorded by Servatrice::statusUpdate() into the - // {prefix}_uptime table. valid is false when no snapshot exists yet. - struct UptimeSnapshot - { - bool valid = false; - quint64 usersCount = 0; - quint64 modsCount = 0; - quint64 gamesCount = 0; - quint64 txBytes = 0; - quint64 rxBytes = 0; - quint64 uptimeSecs = 0; - quint64 timest = 0; - }; - UptimeSnapshot getLatestUptimeSnapshot(int serverId); bool removeUserAvatar(const QString &userName); bool addForgotPassword(const QString &user); bool removeForgotPassword(const QString &user) override; diff --git a/servatrice/src/serversocketinterface.cpp b/servatrice/src/serversocketinterface.cpp index 07bc227f4..99f632776 100644 --- a/servatrice/src/serversocketinterface.cpp +++ b/servatrice/src/serversocketinterface.cpp @@ -20,7 +20,6 @@ #include "serversocketinterface.h" -#include "deck_tag_serialization.h" #include "email_parser.h" #include "main.h" #include "servatrice.h" @@ -31,7 +30,6 @@ #include #include -#include #include #include #include @@ -43,10 +41,8 @@ #include #include #include -#include #include #include -#include #include #include #include @@ -58,10 +54,7 @@ #include #include #include -#include -#include #include -#include #include #include #include @@ -95,11 +88,9 @@ #include #include #include -#include #include #include #include -#include #include #include #include @@ -210,77 +201,6 @@ void AbstractServerSocketInterface::logDebugMessage(const QString &message) logger->logMessage(message, this); } -void AbstractServerSocketInterface::processCommandContainer(const CommandContainer &cont) -{ - QElapsedTimer timer; - timer.start(); - Server_ProtocolHandler::processCommandContainer(cont); - const qint64 elapsedMs = timer.nsecsElapsed() / 1000000; - - // The base dispatch is an if/else-if chain — at most one family is - // actually processed. Recording every family in the container would - // let an unauthenticated client stampforge developer/moderator/admin - // samples by batching them alongside a session command the server - // actually runs. Mirror the base's selection and skip entirely when - // deleted or when no family matched. - if (deleted) { - return; - } - - // When getPbExtension returns -1 (no extension set) and the kind is - // non-zero, typeIdFor wraps into the previous kind's range instead of - // hitting the typeId < 0 guard in observeCommand. Skip such entries. - int kind = -1; - if (cont.game_command_size()) { - kind = 2; - } else if (cont.room_command_size()) { - kind = 1; - } else if (cont.session_command_size()) { - kind = 0; - } else if (cont.moderator_command_size()) { - kind = 3; - } else if (cont.admin_command_size()) { - kind = 4; - } else if (cont.developer_command_size()) { - kind = 5; - } - - if (kind >= 0) { - auto recordDispatched = [&](int familyKind, const auto &cmds) { - for (const auto &cmd : cmds) { - const int ext = getPbExtension(cmd); - if (ext >= 0) { - servatrice->getMetricsRegistry().observeCommand(MetricsRegistry::typeIdFor(familyKind, ext), - elapsedMs); - } - } - }; - - if (kind == 0) { - recordDispatched(kind, cont.session_command()); - } else if (kind == 1) { - recordDispatched(kind, cont.room_command()); - } else if (kind == 2) { - recordDispatched(kind, cont.game_command()); - } else if (kind == 3) { - recordDispatched(kind, cont.moderator_command()); - } else if (kind == 4) { - recordDispatched(kind, cont.admin_command()); - } else { - recordDispatched(kind, cont.developer_command()); - } - } - - const int slowCommandMs = servatrice->getMetricsSlowCommandMs(); - if (slowCommandMs > 0 && elapsedMs >= slowCommandMs) { - const ServerInfo_User *info = getUserInfo(); - const QString user = authState == PasswordRight && info ? QString::fromStdString(info->name()) - : QStringLiteral("unauthenticated"); - qCWarning(AbstractServerSocketInterfaceLog) << "slow command container from" << user << "processed in" - << elapsedMs << "ms (" << cont.ByteSizeLong() << "bytes)"; - } -} - Response::ResponseCode AbstractServerSocketInterface::processExtendedSessionCommand(int cmdType, const SessionCommand &cmd, ResponseContainer &rc) @@ -345,10 +265,6 @@ Response::ResponseCode AbstractServerSocketInterface::processExtendedSessionComm return cmdDeckShareCreate(cmd.GetExtension(Command_DeckShareCreate::ext), rc); case SessionCommand::DECK_SHARE_LIST: return cmdDeckShareList(cmd.GetExtension(Command_DeckShareList::ext), rc); - case SessionCommand::DECK_SHARE_LIST_MINE: - return cmdDeckShareListMine(cmd.GetExtension(Command_DeckShareListMine::ext), rc); - case SessionCommand::DECK_SHARE_REMOVE: - return cmdDeckShareRemove(cmd.GetExtension(Command_DeckShareRemove::ext), rc); case SessionCommand::DECK_SHARE_DOWNLOAD: return cmdDeckShareDownload(cmd.GetExtension(Command_DeckShareDownload::ext), rc); case SessionCommand::ACCOUNT_PASSWORD: @@ -391,7 +307,7 @@ Response::ResponseCode AbstractServerSocketInterface::processExtendedModeratorCo case ModeratorCommand::REPORT_RESOLVE: return cmdReportResolve(cmd.GetExtension(Command_ReportResolve::ext), rc); case ModeratorCommand::VIEWLOG_HISTORY: - return cmdGetLogHistory(cmd.GetExtension(Command_ViewLogHistory::ext), rc, true); + return cmdGetLogHistory(cmd.GetExtension(Command_ViewLogHistory::ext), rc); case ModeratorCommand::GRANT_REPLAY_ACCESS: return cmdGrantReplayAccess(cmd.GetExtension(Command_GrantReplayAccess::ext), rc); case ModeratorCommand::REPLAY_DOWNLOAD_BY_GAME_ID: @@ -444,26 +360,6 @@ AbstractServerSocketInterface::processExtendedAdminCommand(int cmdType, const Ad } } -// DEVELOPER FUNCTIONS. -// Permission is checked by processDeveloperCommandContainer. Only stats-style -// queries live here, never community moderation or server administration. -Response::ResponseCode AbstractServerSocketInterface::processExtendedDeveloperCommand(int cmdType, - const DeveloperCommand &cmd, - ResponseContainer &rc) -{ - switch ((DeveloperCommand::DeveloperCommandType)cmdType) { - case DeveloperCommand::GET_SERVER_STATS: - return cmdGetServerStats(cmd.GetExtension(Command_GetServerStats::ext), rc); - case DeveloperCommand::VIEWLOG_HISTORY: { - // Same query as the moderator log view, carried by the developer - // command family, but narrows out private chats and sender IPs. - return cmdGetLogHistory(cmd.GetExtension(Command_ViewLogHistory::dev_ext), rc, false); - } - default: - return Response::RespFunctionNotAllowed; - } -} - Response::ResponseCode AbstractServerSocketInterface::cmdAddToList(const Command_AddToList &cmd, ResponseContainer &rc) { if (authState != PasswordRight) { @@ -660,9 +556,7 @@ bool AbstractServerSocketInterface::deckListHelper(int folderId, newFile->set_banner_card_name(query->value(4).toString().toStdString()); newFile->set_banner_card_provider(query->value(5).toString().toStdString()); newFile->set_color_identity(query->value(6).toString().toStdString()); - for (const QString &tag : deserializeDeckTags(query->value(7).toString())) { - newFile->add_tags(tag.toStdString()); - } + newFile->set_tags(query->value(7).toString().toStdString()); } return true; @@ -785,11 +679,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckSetVisibility(const return Response::RespNameNotFound; } - query = sqlInterface->prepareQuery("update {prefix}_decklist_files set is_public = :is_public where id = :id " - "and id_user = :id_user"); + query = sqlInterface->prepareQuery("update {prefix}_decklist_files set is_public = :is_public where id = :id"); query->bindValue(":is_public", cmd.is_public() ? 1 : 0); query->bindValue(":id", cmd.deck_id()); - query->bindValue(":id_user", userInfo->id()); if (!sqlInterface->execSqlQuery(query)) { return Response::RespContextError; } @@ -800,11 +692,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckSetVisibility(const } QSqlQuery *query = - sqlInterface->prepareQuery("update {prefix}_decklist_folders set is_public = :is_public where id = :id " - "and id_user = :id_user"); + sqlInterface->prepareQuery("update {prefix}_decklist_folders set is_public = :is_public where id = :id"); query->bindValue(":is_public", cmd.is_public() ? 1 : 0); query->bindValue(":id", folderId); - query->bindValue(":id_user", userInfo->id()); if (!sqlInterface->execSqlQuery(query)) { return Response::RespContextError; } @@ -953,22 +843,6 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckDel(const Command_D return Response::RespOk; } -namespace -{ -/** @brief Keeps only the WUBRG colors from a color identity string, deduplicated. */ -QString sanitizeColorIdentity(const QString &colorIdentity) -{ - QString sanitized; - for (const QChar &color : colorIdentity) { - const QChar upper = color.toUpper(); - if (QStringLiteral("WUBRG").contains(upper) && !sanitized.contains(upper)) { - sanitized.append(upper); - } - } - return sanitized; -} -} // namespace - Response::ResponseCode AbstractServerSocketInterface::cmdDeckUpload(const Command_DeckUpload &cmd, ResponseContainer &rc) { @@ -993,14 +867,6 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckUpload(const Comman deckName = "Unnamed deck"; } - // The server derives the banner card and tags from the deck itself. Only the - // color identity must come from the client, since the server has no card - // database to compute it. All values are bounded to the column sizes. - const QString bannerCardName = deck.getBannerCard().name.left(255); - const QString bannerCardProvider = deck.getBannerCard().providerId.left(32); - const QString tagsJson = serializeDeckTags(deck.getTags()); - const QString colorIdentity = sanitizeColorIdentity(nameFromStdString(cmd.color_identity())); - if (cmd.has_path()) { int folderId = getDeckPathId(nameFromStdString(cmd.path())); if (folderId == -1) { @@ -1017,13 +883,11 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckUpload(const Comman query->bindValue(":name", deckName); query->bindValue(":content", deckStr); query->bindValue(":is_public", cmd.has_is_public() && cmd.is_public() ? 1 : 0); - query->bindValue(":banner_card_name", bannerCardName); - query->bindValue(":banner_card_provider", bannerCardProvider); - query->bindValue(":color_identity", colorIdentity); - query->bindValue(":tags", tagsJson); - if (!sqlInterface->execSqlQuery(query)) { - return Response::RespContextError; - } + query->bindValue(":banner_card_name", nameFromStdString(cmd.banner_card_name())); + query->bindValue(":banner_card_provider", nameFromStdString(cmd.banner_card_provider())); + query->bindValue(":color_identity", nameFromStdString(cmd.color_identity())); + query->bindValue(":tags", nameFromStdString(cmd.tags())); + sqlInterface->execSqlQuery(query); Response_DeckUpload *re = new Response_DeckUpload; ServerInfo_DeckStorage_TreeItem *fileInfo = re->mutable_new_file(); @@ -1042,13 +906,11 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckUpload(const Comman query->bindValue(":id_user", userInfo->id()); query->bindValue(":name", deckName); query->bindValue(":content", deckStr); - query->bindValue(":banner_card_name", bannerCardName); - query->bindValue(":banner_card_provider", bannerCardProvider); - query->bindValue(":color_identity", colorIdentity); - query->bindValue(":tags", tagsJson); - if (!sqlInterface->execSqlQuery(query)) { - return Response::RespContextError; - } + query->bindValue(":banner_card_name", nameFromStdString(cmd.banner_card_name())); + query->bindValue(":banner_card_provider", nameFromStdString(cmd.banner_card_provider())); + query->bindValue(":color_identity", nameFromStdString(cmd.color_identity())); + query->bindValue(":tags", nameFromStdString(cmd.tags())); + sqlInterface->execSqlQuery(query); if (query->numRowsAffected() == 0) { return Response::RespNameNotFound; @@ -1059,9 +921,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckUpload(const Comman "id_user = :id_user"); visibilityQuery->bindValue(":id", cmd.deck_id()); visibilityQuery->bindValue(":id_user", userInfo->id()); - if (!sqlInterface->execSqlQuery(visibilityQuery)) { - return Response::RespContextError; - } + sqlInterface->execSqlQuery(visibilityQuery); const bool isPublic = visibilityQuery->next() && visibilityQuery->value(0).toBool(); Response_DeckUpload *re = new Response_DeckUpload; @@ -1121,7 +981,14 @@ DeckShareItemRecord makeShareItemFromDeck(const DeckList &deck, const QString &c item.tags = deck.getTags(); item.bannerCard = deck.getBannerCard().name; item.gameFormat = deck.getGameFormat(); - item.colorIdentity = sanitizeColorIdentity(colorIdentity); + QString sanitizedColorIdentity; + for (const QChar &color : colorIdentity) { + const QChar upper = color.toUpper(); + if (QStringLiteral("WUBRG").contains(upper) && !sanitizedColorIdentity.contains(upper)) { + sanitizedColorIdentity.append(upper); + } + } + item.colorIdentity = sanitizedColorIdentity; item.content = deck.writeToString_Native(); return item; } @@ -1136,25 +1003,6 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckShareCreate(const C sqlInterface->checkSql(); - const int maxItems = servatrice->getDeckShareMaxDecksPerShare(); - if (maxItems > 0 && cmd.items_size() > maxItems) { - return Response::RespInvalidData; - } - - // Per-user rate limit so share links cannot be used to build an unbounded - // word-of-mouth leak of public decks. - const int maxSharesPerDay = servatrice->getDeckShareMaxSharesPerDay(); - if (maxSharesPerDay > 0) { - QSqlQuery *countQuery = sqlInterface->prepareQuery("select count(*) from {prefix}_deck_share where " - "created_by = :created_by and created_at >= " - "DATE_SUB(NOW(), INTERVAL 1 DAY)"); - countQuery->bindValue(":created_by", userInfo->id()); - if (sqlInterface->execSqlQuery(countQuery) && countQuery->next() && - countQuery->value(0).toInt() >= maxSharesPerDay) { - return Response::RespTooManyRequests; - } - } - QList items; if (cmd.items_size() > 0) { for (const DeckShareItem &shareItem : cmd.items()) { @@ -1182,24 +1030,15 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckShareCreate(const C if (folderId == -1) { return Response::RespNameNotFound; } - - // Drain the deck list before resolving each deck: getDeckFromDatabase - // issues its own query on the same cached statement set. QSqlQuery *query = sqlInterface->prepareQuery("select id from {prefix}_decklist_files where id_folder = " ":id_folder and id_user = :id_user"); query->bindValue(":id_folder", folderId); query->bindValue(":id_user", userInfo->id()); - if (!sqlInterface->execSqlQuery(query)) { - return Response::RespContextError; - } - QList deckIds; + sqlInterface->execSqlQuery(query); while (query->next()) { - deckIds.append(query->value(0).toInt()); - } - for (const int deckId : deckIds) { DeckList *deck; try { - deck = sqlInterface->getDeckFromDatabase(deckId, userInfo->id()); + deck = sqlInterface->getDeckFromDatabase(query->value(0).toInt(), userInfo->id()); } catch (Response::ResponseCode &r) { return r; } @@ -1210,9 +1049,13 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckShareCreate(const C return Response::RespInvalidData; } - if (items.isEmpty() || (maxItems > 0 && items.size() > maxItems)) { + if (items.isEmpty()) { return Response::RespInvalidData; } + const int maxItems = servatrice->getDeckShareMaxDecksPerShare(); + if (items.size() > maxItems) { + return Response::RespTooManyRequests; + } QString shareName = nameFromStdString(cmd.name()); if (shareName.isEmpty()) { @@ -1220,69 +1063,20 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckShareCreate(const C } const QString token = generateShareToken(); - qint64 expiresAt = 0; - if (!sqlInterface->createDeckShare(token, shareName, userInfo->id(), items, servatrice->getDeckShareExpiryDays(), - expiresAt)) { + if (!sqlInterface->createDeckShare(token, shareName, userInfo->id(), items, servatrice->getDeckShareExpiryDays())) { return Response::RespInvalidData; } Response_DeckShareCreate *re = new Response_DeckShareCreate; re->set_token(token.toStdString()); - re->set_expires_at(expiresAt); + re->set_expires_at( + QDateTime::currentDateTimeUtc().addDays(servatrice->getDeckShareExpiryDays()).toSecsSinceEpoch()); re->set_item_count(items.size()); rc.setResponseExtension(re); return Response::RespOk; } -Response::ResponseCode AbstractServerSocketInterface::cmdDeckShareListMine(const Command_DeckShareListMine & /*cmd*/, - ResponseContainer &rc) -{ - if (authState != PasswordRight) { - return Response::RespFunctionNotAllowed; - } - - sqlInterface->checkSql(); - - QList shares; - if (!sqlInterface->getDeckSharesForUser(userInfo->id(), shares)) { - return Response::RespContextError; - } - - Response_DeckShareListMine *re = new Response_DeckShareListMine; - for (const DeckShareSummaryRecord &share : shares) { - ServerInfo_DeckShareSummary *summary = re->add_shares(); - summary->set_id(share.id); - summary->set_name(share.name.toStdString()); - summary->set_creation_time(share.creationTime); - summary->set_expires_at(share.expiresAt); - summary->set_item_count(share.itemCount); - } - rc.setResponseExtension(re); - - return Response::RespOk; -} - -Response::ResponseCode AbstractServerSocketInterface::cmdDeckShareRemove(const Command_DeckShareRemove &cmd, - ResponseContainer & /*rc*/) -{ - if (authState != PasswordRight) { - return Response::RespFunctionNotAllowed; - } - - if (!cmd.has_share_id()) { - return Response::RespInvalidData; - } - - sqlInterface->checkSql(); - - if (!sqlInterface->deleteDeckShare(cmd.share_id(), userInfo->id())) { - return Response::RespNameNotFound; - } - - return Response::RespOk; -} - Response::ResponseCode AbstractServerSocketInterface::cmdDeckShareList(const Command_DeckShareList &cmd, ResponseContainer &rc) { @@ -1619,13 +1413,12 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReplaySubmitCode(const // MODERATOR FUNCTIONS. // May be called by admins and moderators. Permission is checked by the calling function. Response::ResponseCode AbstractServerSocketInterface::cmdGetLogHistory(const Command_ViewLogHistory &cmd, - ResponseContainer &rc, - bool allowPrivateChat) + ResponseContainer &rc) { QList messageList; QString userName = nameFromStdString(cmd.user_name()); - QString ipAddress = allowPrivateChat ? nameFromStdString(cmd.ip_address()) : QString(); + QString ipAddress = nameFromStdString(cmd.ip_address()); QString gameName = nameFromStdString(cmd.game_name()); QString gameID = nameFromStdString(cmd.game_id()); QString message = textFromStdString(cmd.message()); @@ -1640,21 +1433,11 @@ Response::ResponseCode AbstractServerSocketInterface::cmdGetLogHistory(const Com if (nameFromStdString(cmd.log_location(i)).simplified() == "game") { gameType = true; } - if (nameFromStdString(cmd.log_location(i)).simplified() == "chat" && allowPrivateChat) { + if (nameFromStdString(cmd.log_location(i)).simplified() == "chat") { chatType = true; } } - // For callers that must not see private conversations, never leave the - // target-type filter empty: if the request only asked for "chat" (or for - // nothing at all) the query below would carry no target_type restriction - // and would return every row, private messages included. Fall back to the - // game/room diagnostics the caller is allowed to see. - if (!allowPrivateChat && !gameType && !roomType) { - gameType = true; - roomType = true; - } - int dateRange = cmd.date_range(); int maximumResults = cmd.maximum_results(); @@ -1664,11 +1447,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdGetLogHistory(const Com QListIterator messageIterator(sqlInterface->getMessageLogHistory( userName, ipAddress, gameName, gameID, message, chatType, gameType, roomType, dateRange, maximumResults)); while (messageIterator.hasNext()) { - ServerInfo_ChatMessage chatMessage = messageIterator.next(); - if (!allowPrivateChat) { - chatMessage.clear_sender_ip(); - } - re->add_log_message()->CopyFrom(chatMessage); + re->add_log_message()->CopyFrom(messageIterator.next()); } } else { ServerInfo_ChatMessage chatMessage; @@ -2257,79 +2036,6 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportUserInfo(const Co return Response::RespOk; } -Response::ResponseCode AbstractServerSocketInterface::cmdGetServerStats(const Command_GetServerStats & /*cmd */, - ResponseContainer &rc) -{ - if (!sqlInterface->checkSql()) { - return Response::RespInternalError; - } - - // Servatrice::statusUpdate() periodically snapshots server health into the - // uptime table. Serve the freshest snapshot for this server. - const auto snapshot = sqlInterface->getLatestUptimeSnapshot(servatrice->getServerID()); - if (!snapshot.valid) { - // No snapshot yet (fresh server, or statusUpdate() has not ticked). - return Response::RespInternalError; - } - - auto *re = new Response_GetServerStats; - re->set_users_count(snapshot.usersCount); - re->set_mods_count(snapshot.modsCount); - re->set_games_count(snapshot.gamesCount); - re->set_tx_bytes(snapshot.txBytes); - re->set_rx_bytes(snapshot.rxBytes); - re->set_uptime_secs(snapshot.uptimeSecs); - re->set_timest(snapshot.timest); - - // Live metrics from the in-process MetricsRegistry (resets on server restart) - re->set_cards_in_games(static_cast(servatrice->getCardsInGamesTotal())); - re->set_eventloop_stalls_total(static_cast(servatrice->getEventLoopStallsTotal())); - re->set_eventloop_last_stall_ms(static_cast(servatrice->getEventLoopLastStallMs())); - re->set_eventloop_max_stall_ms(static_cast(servatrice->getEventLoopMaxStallMs())); - re->set_total_commands(static_cast(servatrice->getMetricsRegistry().totalCommands())); - re->set_total_command_time_ms( - static_cast(servatrice->getMetricsRegistry().totalTimeMs())); - re->set_active_command_types(servatrice->getMetricsRegistry().activeTypeCount()); - - const auto gameStart = servatrice->getMetricsRegistry().getGameStartSnapshot(); - re->set_game_start_count(static_cast(gameStart.count)); - re->set_game_start_total_ms(static_cast(gameStart.totalMs)); - - // Per-command breakdown: resolve protobuf extension names via the descriptor pool - static const char *messageNames[] = {"SessionCommand", "RoomCommand", "GameCommand", - "ModeratorCommand", "AdminCommand", "DeveloperCommand"}; - const auto activeStats = servatrice->getMetricsRegistry().collectActiveStats(); - for (const auto &stat : activeStats) { - const int kind = stat.typeId / MetricsRegistry::KindStride; - const int number = stat.typeId % MetricsRegistry::KindStride; - - QString label; - if (kind >= 0 && kind < MetricsRegistry::NumKinds) { - const google::protobuf::DescriptorPool *pool = google::protobuf::DescriptorPool::generated_pool(); - const google::protobuf::Descriptor *message = pool->FindMessageTypeByName(messageNames[kind]); - const google::protobuf::FieldDescriptor *extension = - message ? pool->FindExtensionByNumber(message, number) : nullptr; - if (extension) { - label = QString::fromLatin1(MetricsRegistry::KindNames[kind]) + QStringLiteral("/") + - QString::fromStdString(std::string(extension->message_type()->name())); - } - } - if (label.isEmpty()) { - label = QString::number(stat.typeId); - } - - CommandStats *cs = re->add_command_stats(); - cs->set_kind_index(static_cast(kind)); - cs->set_extension_number(static_cast(number)); - cs->set_command_name(label.toStdString()); - cs->set_count(static_cast(stat.count)); - cs->set_total_ms(static_cast(stat.totalMs)); - } - - rc.setResponseExtension(re); - return Response::RespOk; -} - Response::ResponseCode AbstractServerSocketInterface::cmdReportStats(const Command_ReportStats & /*cmd */, ResponseContainer &rc) { @@ -3902,7 +3608,7 @@ bool AbstractServerSocketInterface::removeAdminFlagFromUser(const QString &userN if (user) { Event_ConnectionClosed event; event.set_reason(Event_ConnectionClosed::DEMOTED); - event.set_reason_str("Your moderator, judge, and/or developer status has been revoked."); + event.set_reason_str("Your moderator and/or judge status has been revoked."); event.set_end_time(QDateTime::currentDateTime().toSecsSinceEpoch()); SessionEvent *se = user->prepareSessionEvent(event); @@ -3944,18 +3650,6 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAdjustMod(const Command } } - if (cmd.has_should_be_developer()) { - if (cmd.should_be_developer()) { - if (!addAdminFlagToUser(userName, 8)) { - return Response::RespInternalError; - } - } else { - if (!removeAdminFlagFromUser(userName, 8)) { - return Response::RespInternalError; - } - } - } - return Response::RespOk; } diff --git a/servatrice/src/serversocketinterface.h b/servatrice/src/serversocketinterface.h index 36b900d28..47657f5ec 100644 --- a/servatrice/src/serversocketinterface.h +++ b/servatrice/src/serversocketinterface.h @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #include @@ -55,8 +54,6 @@ class Command_DeckUpload; class Command_DeckSetVisibility; class Command_DeckShareCreate; class Command_DeckShareList; -class Command_DeckShareListMine; -class Command_DeckShareRemove; class Command_DeckShareDownload; class Command_ReplayList; class Command_ReplayDownload; @@ -89,7 +86,6 @@ signals: protected: void logDebugMessage(const QString &message) override; bool tooManyRegistrationAttempts(const QString &ipAddress); - void processCommandContainer(const CommandContainer &cont) override; virtual void writeToSocket(QByteArray &data) = 0; virtual void flushSocket() = 0; @@ -126,8 +122,6 @@ private: Response::ResponseCode cmdDeckDownloadPublic(const Command_DeckDownloadPublic &cmd, ResponseContainer &rc); Response::ResponseCode cmdDeckShareCreate(const Command_DeckShareCreate &cmd, ResponseContainer &rc); Response::ResponseCode cmdDeckShareList(const Command_DeckShareList &cmd, ResponseContainer &rc); - Response::ResponseCode cmdDeckShareListMine(const Command_DeckShareListMine &cmd, ResponseContainer &rc); - Response::ResponseCode cmdDeckShareRemove(const Command_DeckShareRemove &cmd, ResponseContainer &rc); Response::ResponseCode cmdDeckShareDownload(const Command_DeckShareDownload &cmd, ResponseContainer &rc); Response::ResponseCode cmdReplayList(const Command_ReplayList &cmd, ResponseContainer &rc); Response::ResponseCode cmdReplayDownload(const Command_ReplayDownload &cmd, ResponseContainer &rc); @@ -139,8 +133,7 @@ private: Response::ResponseCode cmdBanFromServer(const Command_BanFromServer &cmd, ResponseContainer &rc); Response::ResponseCode cmdReportList(const Command_ReportList &cmd, ResponseContainer &rc); Response::ResponseCode cmdWarnUser(const Command_WarnUser &cmd, ResponseContainer &rc); - Response::ResponseCode - cmdGetLogHistory(const Command_ViewLogHistory &cmd, ResponseContainer &rc, bool allowPrivateChat); + Response::ResponseCode cmdGetLogHistory(const Command_ViewLogHistory &cmd, ResponseContainer &rc); Response::ResponseCode cmdGetBanHistory(const Command_GetBanHistory &cmd, ResponseContainer &rc); Response::ResponseCode cmdGetWarnList(const Command_GetWarnList &cmd, ResponseContainer &rc); Response::ResponseCode cmdGetWarnHistory(const Command_GetWarnHistory &cmd, ResponseContainer &rc); @@ -176,8 +169,6 @@ private: processExtendedModeratorCommand(int cmdType, const ModeratorCommand &cmd, ResponseContainer &rc) override; Response::ResponseCode processExtendedAdminCommand(int cmdType, const AdminCommand &cmd, ResponseContainer &rc) override; - Response::ResponseCode - processExtendedDeveloperCommand(int cmdType, const DeveloperCommand &cmd, ResponseContainer &rc) override; Response::ResponseCode cmdAccountEdit(const Command_AccountEdit &cmd, ResponseContainer &rc); Response::ResponseCode cmdAccountImage(const Command_AccountImage &cmd, ResponseContainer &rc); @@ -199,8 +190,6 @@ private: Response::ResponseCode cmdResetUserPassword(const Command_ResetUserPassword &cmd, ResponseContainer &rc); Response::ResponseCode cmdRemoveUserAvatar(const Command_RemoveUserAvatar &cmd, ResponseContainer &rc); - Response::ResponseCode cmdGetServerStats(const Command_GetServerStats &cmd, ResponseContainer &rc); - bool addAdminFlagToUser(const QString &user, int flag); bool removeAdminFlagFromUser(const QString &user, int flag); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index dfbdabb5e..18ab60d06 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -11,12 +11,9 @@ add_test(NAME playmat_resolver_test COMMAND playmat_resolver_test) 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 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) @@ -33,23 +30,10 @@ add_executable(deck_hash_performance_test deck_hash_performance_test.cpp) 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(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) @@ -86,12 +70,9 @@ if(NOT GTEST_FOUND) add_dependencies(server_card_counter_test gtest) add_dependencies(server_counter_test gtest) add_dependencies(server_rate_limiter_test gtest) - add_dependencies(server_developer_role_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}) @@ -123,10 +104,6 @@ target_link_libraries( target_link_libraries( server_rate_limiter_test libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} ) -target_link_libraries( - server_developer_role_test libcockatrice_network 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} ) @@ -134,11 +111,6 @@ target_link_libraries(lag_monitor_test Threads::Threads ${GTEST_BOTH_LIBRARIES} target_link_libraries( latency_tracker_test libcockatrice_network Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} ) -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/card_zone_algorithms/card_zone_algorithms_test.cpp b/tests/card_zone_algorithms/card_zone_algorithms_test.cpp index 8fab42566..cc098cae9 100644 --- a/tests/card_zone_algorithms/card_zone_algorithms_test.cpp +++ b/tests/card_zone_algorithms/card_zone_algorithms_test.cpp @@ -134,35 +134,6 @@ TEST_F(AddCardAlgorithmTest, MidListInsertionPreservesOrder) EXPECT_EQ(knownList.at(2), &b); } -// Reconnecting to a game rebuilds zones from a ServerInfo_Zone. Non-coordinate zones -// (hand, piles, stack) report x == 0 on every card, so inserting each rebuilt card at -// that index would reverse the received server order. Appending (-1) keeps it. -TEST_F(AddCardAlgorithmTest, RebuildInsertAtZeroReversesServerOrder) -{ - MockCard a, b, c; - CardZoneAlgorithms::addCardToList(knownList, &a, 0, false); - CardZoneAlgorithms::addCardToList(knownList, &b, 0, false); - CardZoneAlgorithms::addCardToList(knownList, &c, 0, false); - - EXPECT_EQ(knownList.size(), 3); - EXPECT_EQ(knownList.at(0), &c); - EXPECT_EQ(knownList.at(1), &b); - EXPECT_EQ(knownList.at(2), &a); -} - -TEST_F(AddCardAlgorithmTest, RebuildAppendPreservesServerOrder) -{ - MockCard a, b, c; - CardZoneAlgorithms::addCardToList(knownList, &a, -1, false); - CardZoneAlgorithms::addCardToList(knownList, &b, -1, false); - CardZoneAlgorithms::addCardToList(knownList, &c, -1, false); - - EXPECT_EQ(knownList.size(), 3); - EXPECT_EQ(knownList.at(0), &a); - EXPECT_EQ(knownList.at(1), &b); - EXPECT_EQ(knownList.at(2), &c); -} - TEST_F(AddCardAlgorithmTest, KeepAnnotationsFalsePassedThrough) { MockCard card; 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/metrics_registry_test.cpp b/tests/metrics_registry_test.cpp deleted file mode 100644 index 1abef58e1..000000000 --- a/tests/metrics_registry_test.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include -#include -#include -#include - -TEST(MetricsRegistryTest, EmptyRegistryHasZeroedCounters) -{ - MetricsRegistry registry; - - EXPECT_EQ(0, registry.totalCommands()); - EXPECT_EQ(0, registry.totalTimeMs()); - EXPECT_EQ(0, registry.activeTypeCount()); - EXPECT_EQ(0, registry.getGameStartSnapshot().count); -} - -TEST(MetricsRegistryTest, SampleIsRecordedInTotalsAndSlot) -{ - MetricsRegistry registry; - registry.observeCommand(MetricsRegistry::typeIdFor(0, 1000), 7); - - EXPECT_EQ(1, registry.totalCommands()); - EXPECT_EQ(7, registry.totalTimeMs()); - EXPECT_EQ(1, registry.activeTypeCount()); - - const auto stats = registry.collectActiveStats(); - ASSERT_EQ(1, stats.size()); - EXPECT_EQ(MetricsRegistry::typeIdFor(0, 1000), stats[0].typeId); - EXPECT_EQ(1, stats[0].count); - EXPECT_EQ(7, stats[0].totalMs); -} - -TEST(MetricsRegistryTest, KindEncodingSeparatesSameExtensionNumber) -{ - MetricsRegistry registry; - const int sessionPing = MetricsRegistry::typeIdFor(0, 1000); - const int roomLeaveRoom = MetricsRegistry::typeIdFor(1, 1000); - ASSERT_NE(sessionPing, roomLeaveRoom); - - registry.observeCommand(sessionPing, 1); - registry.observeCommand(roomLeaveRoom, 5000); - - EXPECT_EQ(2, registry.activeTypeCount()); -} - -TEST(MetricsRegistryTest, OutOfRangeIdsLandInOverflowSlot) -{ - MetricsRegistry registry; - registry.observeCommand(-1, 4); - registry.observeCommand(MetricsRegistry::MaxTypes + 12345, 4); - - EXPECT_EQ(2, registry.totalCommands()); - EXPECT_EQ(1, registry.activeTypeCount()); // both collapsed into one slot - EXPECT_EQ(8, registry.totalTimeMs()); -} - -TEST(MetricsRegistryTest, NegativeDurationsAreClamped) -{ - MetricsRegistry registry; - registry.observeCommand(MetricsRegistry::typeIdFor(0, 1000), -50); - - EXPECT_EQ(0, registry.totalTimeMs()); -} - -TEST(MetricsRegistryTest, GameStartTrackedSeparatelyFromCommands) -{ - MetricsRegistry registry; - registry.observeGameStartDurationMs(120); - - EXPECT_EQ(0, registry.totalCommands()); - EXPECT_EQ(0, registry.totalTimeMs()); - EXPECT_EQ(0, registry.activeTypeCount()); - - const auto snapshot = registry.getGameStartSnapshot(); - EXPECT_EQ(1, snapshot.count); - EXPECT_EQ(120, snapshot.totalMs); -} - -int main(int argc, char **argv) -{ - QCoreApplication app(argc, argv); - ::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..3834be9ed 100644 --- a/tests/oracle/oracle_importer_test.cpp +++ b/tests/oracle/oracle_importer_test.cpp @@ -4,8 +4,6 @@ #include #include #include -#include -#include #include #include #include @@ -72,18 +70,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; @@ -755,372 +741,6 @@ TEST_F(OracleImporterTest, StartImportParsesSetsLazily) ASSERT_FALSE(importer->getCardList().value("Lazy Import Card").isNull()); } -// ============================================================================ -// Scan progress reporting tests -// ============================================================================ - -TEST(OracleScanProgress, ScanProgressReportsMonotonicBytesToTotal) -{ - QJsonObject setObj; - setObj["code"] = "tst"; - setObj["name"] = "Test Set"; - setObj["type"] = "expansion"; - setObj["releaseDate"] = "2024-01-01"; - QJsonArray cards; - for (int i = 0; i < 40; ++i) { - QJsonObject card; - card["name"] = QString("Card %1").arg(i); - card["text"] = "Some rules text used to bulk up the card payload."; - card["layout"] = "normal"; - cards.append(card); - } - setObj["cards"] = cards; - - QJsonObject root; - root["data"] = QJsonObject{{"TST", setObj}}; - - const QByteArray data = QJsonDocument(root).toJson(QJsonDocument::Compact); - - QList> reports; - RawJson::ScanError error; - const QList ranges = - RawJson::scanSetRanges(data, &error, [&reports](qsizetype bytesRead, qsizetype totalBytes) { - reports.append({bytesRead, totalBytes}); - }); - - ASSERT_FALSE(error.isError()) << error.message.toStdString(); - ASSERT_EQ(ranges.size(), 1); - ASSERT_FALSE(reports.isEmpty()); - ASSERT_GT(reports.size(), 1); - - qsizetype last = 0; - for (const auto &[bytesRead, totalBytes] : reports) { - ASSERT_EQ(totalBytes, data.size()); - ASSERT_GE(bytesRead, last) << "scan progress must be monotonic"; - ASSERT_LE(bytesRead, totalBytes) << "scan progress must not overshoot the document size"; - last = bytesRead; - } - ASSERT_EQ(reports.constLast().first, data.size()) << "scan must end at 100%"; - ASSERT_LE(reports.size(), 160) << "scan reports must be throttled"; -} - -TEST(OracleScanProgress, ScanWithoutCallbackStillParses) -{ - QJsonObject setObj; - setObj["code"] = "tst"; - setObj["name"] = "Test Set"; - setObj["type"] = "expansion"; - setObj["releaseDate"] = "2024-01-01"; - setObj["cards"] = QJsonArray(); - - QJsonObject root; - root["data"] = QJsonObject{{"TST", setObj}}; - - const QByteArray data = QJsonDocument(root).toJson(QJsonDocument::Compact); - - RawJson::ScanError error; - const QList ranges = RawJson::scanSetRanges(data, &error); - - ASSERT_FALSE(error.isError()) << error.message.toStdString(); - ASSERT_EQ(ranges.size(), 1); - ASSERT_EQ(ranges.first().code, "tst"); -} - -TEST_F(OracleImporterTest, ReadSetsFromByteArrayEmitsScanProgress) -{ - QJsonObject setObj; - setObj["code"] = "tst"; - setObj["name"] = "Test Set"; - setObj["type"] = "expansion"; - setObj["releaseDate"] = "2024-01-01"; - QJsonArray cards; - for (int i = 0; i < 40; ++i) { - QJsonObject card; - card["name"] = QString("Card %1").arg(i); - cards.append(card); - } - setObj["cards"] = cards; - - QJsonObject root; - root["data"] = QJsonObject{{"TST", setObj}}; - - const QByteArray data = QJsonDocument(root).toJson(QJsonDocument::Compact); - - QList> emissions; - QObject::connect(importer, &OracleImporter::dataReadProgress, - [&emissions](int bytesRead, int totalBytes) { emissions.append({bytesRead, totalBytes}); }); - - ASSERT_TRUE(importer->readSetsFromByteArray(data)); - ASSERT_FALSE(emissions.isEmpty()); - for (const auto &[bytesRead, totalBytes] : emissions) { - ASSERT_EQ(totalBytes, data.size()); - ASSERT_GE(bytesRead, 0); - ASSERT_LE(bytesRead, totalBytes); - } - ASSERT_EQ(emissions.constLast().first, data.size()); -} - -TEST_F(OracleImporterTest, DisablingProgressReportingSuppressesScanEmissions) -{ - QJsonObject setObj; - setObj["code"] = "tst"; - setObj["name"] = "Test Set"; - setObj["type"] = "expansion"; - setObj["releaseDate"] = "2024-01-01"; - setObj["cards"] = QJsonArray(); - QJsonObject root; - root["data"] = QJsonObject{{"TST", setObj}}; - const QByteArray data = QJsonDocument(root).toJson(QJsonDocument::Compact); - - int emissions = 0; - QObject::connect(importer, &OracleImporter::dataReadProgress, [&emissions](int, int) { ++emissions; }); - - importer->setProgressReporting(false); - ASSERT_TRUE(importer->readSetsFromByteArray(data)); - ASSERT_EQ(emissions, 0); - - importer->setProgressReporting(true); - ASSERT_TRUE(importer->readSetsFromByteArray(data)); - 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_developer_role_test.cpp b/tests/server_developer_role_test.cpp deleted file mode 100644 index 127f606a8..000000000 --- a/tests/server_developer_role_test.cpp +++ /dev/null @@ -1,137 +0,0 @@ -/** @file server_developer_role_test.cpp - * @brief Tests for the developer staff role authorization and dispatch. - * @ingroup Tests - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -// The server_remote library references the global RNG, which is normally -// defined by the servatrice/client executable main(). Provide a stub so the -// unit test can link against it. -RNG_Abstract *rng = nullptr; - -namespace -{ - -class TestDeveloperHandler : public Server_ProtocolHandler -{ -public: - explicit TestDeveloperHandler(Server *_server) : Server_ProtocolHandler(_server, nullptr) - { - } - - QString getAddress() const override - { - return {}; - } - QString getConnectionType() const override - { - return {}; - } - - // Buffer the last response code sent to the client so tests can assert on - // the outcome of processCommandContainer(). - Response::ResponseCode lastResponseCode = Response::RespNothing; - int dispatchCount = 0; - -protected: - void transmitProtocolItem(const ServerMessage &item) override - { - if (item.message_type() == ServerMessage::RESPONSE) { - lastResponseCode = item.response().response_code(); - } - } - - Response::ResponseCode - processExtendedDeveloperCommand(int cmdType, const DeveloperCommand &, ResponseContainer &) override - { - ++dispatchCount; - // Fail closed for anything not explicitly handled. - if (cmdType != DeveloperCommand::GET_SERVER_STATS) { - return Response::RespFunctionNotAllowed; - } - return Response::RespOk; - } -}; - -class DeveloperRoleTest : public ::testing::Test -{ -protected: - Server server; - TestDeveloperHandler handler{&server}; - - void setUserLevel(uint32_t level) - { - ServerInfo_User user; - user.set_user_level(level); - handler.setUserInfo(user); - } -}; - -TEST_F(DeveloperRoleTest, RejectsWhenNotLoggedIn) -{ - CommandContainer cont; - cont.add_developer_command(); - handler.processCommandContainer(cont); - EXPECT_EQ(handler.lastResponseCode, Response::RespLoginNeeded); - EXPECT_EQ(handler.dispatchCount, 0); -} - -TEST_F(DeveloperRoleTest, RejectsPlainUser) -{ - setUserLevel(ServerInfo_User::IsUser | ServerInfo_User::IsRegistered); - - CommandContainer cont; - cont.add_developer_command(); - handler.processCommandContainer(cont); - EXPECT_EQ(handler.lastResponseCode, Response::RespLoginNeeded); - EXPECT_EQ(handler.dispatchCount, 0); -} - -TEST_F(DeveloperRoleTest, RejectsModeratorThatIsNotDeveloper) -{ - setUserLevel(ServerInfo_User::IsModerator); - - CommandContainer cont; - cont.add_developer_command(); - handler.processCommandContainer(cont); - EXPECT_EQ(handler.lastResponseCode, Response::RespLoginNeeded); -} - -TEST_F(DeveloperRoleTest, DispatchesToDeveloperCommandForDeveloper) -{ - setUserLevel(ServerInfo_User::IsDeveloper); - - CommandContainer cont; - DeveloperCommand *cmd = cont.add_developer_command(); - cmd->MutableExtension(Command_GetServerStats::ext); - handler.processCommandContainer(cont); - EXPECT_EQ(handler.lastResponseCode, Response::RespOk); - EXPECT_EQ(handler.dispatchCount, 1); -} - -TEST_F(DeveloperRoleTest, FailClosedForUnknownDeveloperCommand) -{ - setUserLevel(ServerInfo_User::IsDeveloper); - - CommandContainer cont; - cont.add_developer_command(); // no extension set -> getPbExtension() returns -1 - handler.processCommandContainer(cont); - EXPECT_EQ(handler.lastResponseCode, Response::RespFunctionNotAllowed); - EXPECT_EQ(handler.dispatchCount, 1); -} - -} // namespace - -int main(int argc, char **argv) -{ - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/tests/settings/settings_defaults_test.cpp b/tests/settings/settings_defaults_test.cpp index c8c5ad4c8..139656f27 100644 --- a/tests/settings/settings_defaults_test.cpp +++ b/tests/settings/settings_defaults_test.cpp @@ -300,12 +300,6 @@ TEST_F(SettingsDefaultsTest, Chat_RoomHistory_Default) ASSERT_EQ(s.getRoomHistory(), true); } -TEST_F(SettingsDefaultsTest, Chat_IgnoreAllPrivateMessages_Default) -{ - ChatSettings s(settingsPath, nullptr); - ASSERT_EQ(s.getIgnoreAllPrivateMessages(), false); -} - // --- PersonalSettings --- TEST_F(SettingsDefaultsTest, Personal_Lang_Default) @@ -386,19 +380,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 +543,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)