diff --git a/.ci/compile.sh b/.ci/compile.sh index 6c8a7db18..a7c349fb0 100755 --- a/.ci/compile.sh +++ b/.ci/compile.sh @@ -156,6 +156,7 @@ function ccachestatsverbose() { # Compile if [[ $RUNNER_OS == macOS ]]; then + if [[ $TARGET_MACOS_VERSION ]]; then # CMAKE_OSX_DEPLOYMENT_TARGET is a vanilla cmake flag needed to compile to target macOS version flags+=("-DCMAKE_OSX_DEPLOYMENT_TARGET=$TARGET_MACOS_VERSION") @@ -202,17 +203,18 @@ if [[ $RUNNER_OS == macOS ]]; then hdiutil_script="/tmp/hdiutil.sh" # shellcheck disable=SC2016 echo '#!/bin/bash -i=0 -while ! hdiutil "$@"; do - if (( ++i >= 10 )); then - echo "Error: hdiutil failed $i times!" >&2 - break - fi - sleep 1 -done' >"$hdiutil_script" + i=0 + while ! hdiutil "$@"; do + if (( ++i >= 10 )); then + echo "Error: hdiutil failed $i times!" >&2 + break + fi + sleep 1 + done' >"$hdiutil_script" chmod +x "$hdiutil_script" flags+=(-DCPACK_COMMAND_HDIUTIL="$hdiutil_script") fi + elif [[ $RUNNER_OS == Windows ]]; then # Enable MTT, see https://devblogs.microsoft.com/cppblog/improved-parallelism-in-msbuild/ # and https://devblogs.microsoft.com/cppblog/cpp-build-throughput-investigation-and-tune-up/#multitooltask-mtt @@ -242,6 +244,19 @@ if [[ $USE_CCACHE ]]; then echo "::endgroup::" fi +if [[ $RUNNER_OS == macOS ]]; then + echo "::group::Inspect Mach-O binaries" + for app in cockatrice oracle servatrice dbconverter; do + binary="$GITHUB_WORKSPACE/build/$app/$app.app/Contents/MacOS/$app" + echo "Inspecting $app..." + vtool -show-build "$binary" + file "$binary" + lipo -info "$binary" + echo "" + done + echo "::endgroup::" +fi + if [[ $MAKE_TEST ]]; then echo "::group::Run tests" ctest -C "$BUILDTYPE" --output-on-failure @@ -256,7 +271,6 @@ fi if [[ $MAKE_PACKAGE ]]; then echo "::group::Create package" - cmake --build . --target package --config "$BUILDTYPE" echo "::endgroup::" diff --git a/.github/workflows/documentation-build.yml b/.github/workflows/documentation-build.yml index 933f43d28..ef0f0af91 100644 --- a/.github/workflows/documentation-build.yml +++ b/.github/workflows/documentation-build.yml @@ -11,6 +11,9 @@ on: - 'doxygen_style.css' workflow_dispatch: +env: + COCKATRICE_REF: ${{ github.ref_name }} # Tag name if the commit is tagged, otherwise branch name + jobs: docs: name: Doxygen diff --git a/Doxyfile b/Doxyfile index a1861f64f..6a09e2619 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1,4 +1,5 @@ # Doxyfile 1.14.0 +# Doxygen Docs: https://www.doxygen.nl/manual # This file describes the settings to be used by the documentation system # Doxygen (www.doxygen.org) for a project. @@ -48,13 +49,13 @@ PROJECT_NAME = "Cockatrice" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2.11 +PROJECT_NUMBER = $(COCKATRICE_REF) # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewers a # quick idea about the purpose of the project. Keep the description short. -PROJECT_BRIEF = +PROJECT_BRIEF = A cross-platform virtual tabletop for multiplayer card games # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 @@ -839,7 +840,7 @@ FILE_VERSION_FILTER = # DoxygenLayout.xml, Doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. -LAYOUT_FILE = DoxygenLayout.xml +LAYOUT_FILE = doc/doxygen/DoxygenLayout.xml # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib @@ -1438,7 +1439,7 @@ HTML_EXTRA_FILES = doc/doxygen/js/graph_toggle.js # The default value is: AUTO_LIGHT. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_COLORSTYLE = DARK +HTML_COLORSTYLE = AUTO_DARK # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to @@ -2021,7 +2022,7 @@ EXTRA_SEARCH_MAPPINGS = # If the GENERATE_LATEX tag is set to YES, Doxygen will generate LaTeX output. # The default value is: YES. -GENERATE_LATEX = YES +GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 995b55258..d77d70d36 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -180,6 +180,7 @@ set(cockatrice_SOURCES src/interface/widgets/replay/replay_timeline_widget.cpp src/interface/widgets/server/chat_view/chat_view.cpp src/interface/widgets/server/game_selector.cpp + src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp src/interface/widgets/server/games_model.cpp src/interface/widgets/server/handle_public_servers.cpp src/interface/widgets/server/remote/remote_decklist_tree_widget.cpp diff --git a/cockatrice/src/client/network/parsers/interface_json_deck_parser.h b/cockatrice/src/client/network/parsers/interface_json_deck_parser.h index 1c8cbbed2..8ac05a84b 100644 --- a/cockatrice/src/client/network/parsers/interface_json_deck_parser.h +++ b/cockatrice/src/client/network/parsers/interface_json_deck_parser.h @@ -48,7 +48,7 @@ public: } loader->getDeckList()->loadFromStream_Plain(outStream, false); - loader->resolveSetNameAndNumberToProviderID(); + DeckLoader::resolveSetNameAndNumberToProviderID(loader->getDeckList()); return loader; } @@ -95,7 +95,7 @@ public: } loader->getDeckList()->loadFromStream_Plain(outStream, false); - loader->resolveSetNameAndNumberToProviderID(); + DeckLoader::resolveSetNameAndNumberToProviderID(loader->getDeckList()); QJsonObject commandersObj = obj.value("commanders").toObject(); if (!commandersObj.isEmpty()) { diff --git a/cockatrice/src/game/board/abstract_card_item.cpp b/cockatrice/src/game/board/abstract_card_item.cpp index 48720aa96..d5538011d 100644 --- a/cockatrice/src/game/board/abstract_card_item.cpp +++ b/cockatrice/src/game/board/abstract_card_item.cpp @@ -60,7 +60,8 @@ void AbstractCardItem::refreshCardInfo() exactCard = CardDatabaseManager::query()->getCard(cardRef); if (!exactCard && !cardRef.name.isEmpty()) { - auto info = CardInfo::newInstance(cardRef.name, "", true, {}, {}, {}, {}, false, false, -1, false); + CardInfo::UiAttributes attributes = {.tableRow = -1}; + auto info = CardInfo::newInstance(cardRef.name, "", true, {}, {}, {}, {}, attributes); exactCard = ExactCard(info); } if (exactCard) { diff --git a/cockatrice/src/game/board/arrow_item.cpp b/cockatrice/src/game/board/arrow_item.cpp index fd044d042..22e3ceb50 100644 --- a/cockatrice/src/game/board/arrow_item.cpp +++ b/cockatrice/src/game/board/arrow_item.cpp @@ -238,8 +238,8 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) if (startZone->getName().compare("hand") == 0) { startCard->playCard(false); CardInfoPtr ci = startCard->getCard().getCardPtr(); - if (ci && ((!SettingsCache::instance().getPlayToStack() && ci->getTableRow() == 3) || - (SettingsCache::instance().getPlayToStack() && ci->getTableRow() != 0 && + if (ci && ((!SettingsCache::instance().getPlayToStack() && ci->getUiAttributes().tableRow == 3) || + (SettingsCache::instance().getPlayToStack() && ci->getUiAttributes().tableRow != 0 && startCard->getZone()->getName().toStdString() != "stack"))) cmd.set_start_zone("stack"); else diff --git a/cockatrice/src/game/player/player_actions.cpp b/cockatrice/src/game/player/player_actions.cpp index 1fc91c492..e6efaf854 100644 --- a/cockatrice/src/game/player/player_actions.cpp +++ b/cockatrice/src/game/player/player_actions.cpp @@ -59,7 +59,7 @@ void PlayerActions::playCard(CardItem *card, bool faceDown) const CardInfo &info = exactCard.getInfo(); - int tableRow = info.getTableRow(); + int tableRow = info.getUiAttributes().tableRow; bool playToStack = SettingsCache::instance().getPlayToStack(); QString currentZone = card->getZone()->getName(); if (currentZone == "stack" && tableRow == 3) { @@ -72,13 +72,13 @@ void PlayerActions::playCard(CardItem *card, bool faceDown) cmd.set_x(-1); cmd.set_y(0); } else { - tableRow = faceDown ? 2 : info.getTableRow(); + tableRow = faceDown ? 2 : info.getUiAttributes().tableRow; QPoint gridPoint = QPoint(-1, TableZone::clampValidTableRow(2 - tableRow)); cardToMove->set_face_down(faceDown); if (!faceDown) { cardToMove->set_pt(info.getPowTough().toStdString()); } - cardToMove->set_tapped(!faceDown && info.getCipt()); + cardToMove->set_tapped(!faceDown && info.getUiAttributes().cipt); if (tableRow != 3) cmd.set_target_zone("table"); cmd.set_x(gridPoint.x()); @@ -111,7 +111,7 @@ void PlayerActions::playCardToTable(const CardItem *card, bool faceDown) const CardInfo &info = exactCard.getInfo(); - int tableRow = faceDown ? 2 : info.getTableRow(); + int tableRow = faceDown ? 2 : info.getUiAttributes().tableRow; // default instant/sorcery cards to the noncreatures row if (tableRow > 2) { tableRow = 1; @@ -122,7 +122,7 @@ void PlayerActions::playCardToTable(const CardItem *card, bool faceDown) if (!faceDown) { cardToMove->set_pt(info.getPowTough().toStdString()); } - cardToMove->set_tapped(!faceDown && info.getCipt()); + cardToMove->set_tapped(!faceDown && info.getUiAttributes().cipt); cmd.set_target_zone("table"); cmd.set_x(gridPoint.x()); cmd.set_y(gridPoint.y()); @@ -859,7 +859,7 @@ void PlayerActions::actCreateToken() ExactCard correctedCard = CardDatabaseManager::query()->guessCard({lastTokenInfo.name, lastTokenInfo.providerId}); if (correctedCard) { lastTokenInfo.name = correctedCard.getName(); - lastTokenTableRow = TableZone::clampValidTableRow(2 - correctedCard.getInfo().getTableRow()); + lastTokenTableRow = TableZone::clampValidTableRow(2 - correctedCard.getInfo().getUiAttributes().tableRow); if (lastTokenInfo.pt.isEmpty()) { lastTokenInfo.pt = correctedCard.getInfo().getPowTough(); } @@ -910,7 +910,7 @@ void PlayerActions::setLastToken(CardInfoPtr cardInfo) .providerId = SettingsCache::instance().cardOverrides().getCardPreferenceOverride(cardInfo->getName())}; - lastTokenTableRow = TableZone::clampValidTableRow(2 - cardInfo->getTableRow()); + lastTokenTableRow = TableZone::clampValidTableRow(2 - cardInfo->getUiAttributes().tableRow); utilityMenu->setAndEnableCreateAnotherTokenAction(tr("C&reate another %1 token").arg(lastTokenInfo.name)); } @@ -1080,7 +1080,7 @@ void PlayerActions::createCard(const CardItem *sourceCard, // get the target token's location // TODO: Define this QPoint into its own function along with the one below - QPoint gridPoint = QPoint(-1, TableZone::clampValidTableRow(2 - cardInfo->getTableRow())); + QPoint gridPoint = QPoint(-1, TableZone::clampValidTableRow(2 - cardInfo->getUiAttributes().tableRow)); // create the token for the related card Command_CreateToken cmd; diff --git a/cockatrice/src/game/zones/logic/table_zone_logic.cpp b/cockatrice/src/game/zones/logic/table_zone_logic.cpp index 39e24b5cd..42caf2ec8 100644 --- a/cockatrice/src/game/zones/logic/table_zone_logic.cpp +++ b/cockatrice/src/game/zones/logic/table_zone_logic.cpp @@ -18,7 +18,7 @@ void TableZoneLogic::addCardImpl(CardItem *card, int _x, int _y) if (!card->getFaceDown() && card->getPT().isEmpty()) { card->setPT(card->getCardInfo().getPowTough()); } - if (card->getCardInfo().getCipt() && card->getCardInfo().getLandscapeOrientation()) { + if (card->getCardInfo().getUiAttributes().cipt && card->getCardInfo().getUiAttributes().landscapeOrientation) { card->setDoesntUntap(true); } card->setGridPoint(QPoint(_x, _y)); 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 aebb09f0f..296355940 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp @@ -154,7 +154,7 @@ void CardPictureLoader::imageLoaded(const ExactCard &card, const QImage &image) qCDebug(CardPictureLoaderLog) << "Caching NULL pixmap for" << card.getName(); QPixmapCache::insert(card.getPixmapCacheKey(), QPixmap()); } else { - if (card.getInfo().getUpsideDownArt()) { + if (card.getInfo().getUiAttributes().upsideDownArt) { #if (QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)) QImage mirrorImage = image.flipped(Qt::Horizontal | Qt::Vertical); #else diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp index b33aa5881..ebc951c6e 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.cpp +++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp @@ -276,9 +276,11 @@ static QString toDecklistExportString(const DecklistCardNode *card) /** * Export deck to decklist function, called to format the deck in a way to be sent to a server + * + * @param deckList The decklist to export * @param website The website we're sending the deck to */ -QString DeckLoader::exportDeckToDecklist(DecklistWebsite website) +QString DeckLoader::exportDeckToDecklist(const DeckList *deckList, DecklistWebsite website) { // Add the base url QString deckString = "https://" + getDomainForWebsite(website) + "/?"; @@ -345,8 +347,10 @@ struct SetProviderIdToPreferred /** * This function iterates through each card in the decklist and sets the providerId * on each card based on its set name and collector number. + * + * @param deckList The decklist to modify */ -void DeckLoader::setProviderIdToPreferredPrinting() +void DeckLoader::setProviderIdToPreferredPrinting(const DeckList *deckList) { // Set up the struct to call. SetProviderIdToPreferred setProviderIdToPreferred; @@ -357,8 +361,10 @@ void DeckLoader::setProviderIdToPreferredPrinting() /** * Sets the providerId on each card in the decklist based on its set name and collector number. + * + * @param deckList The decklist to modify */ -void DeckLoader::resolveSetNameAndNumberToProviderID() +void DeckLoader::resolveSetNameAndNumberToProviderID(const DeckList *deckList) { auto setProviderId = [](const auto node, const auto card) { Q_UNUSED(node); @@ -397,8 +403,10 @@ struct ClearSetNameNumberAndProviderId /** * Clears the set name and numbers on each card in the decklist. + * + * @param deckList The decklist to modify */ -void DeckLoader::clearSetNamesAndNumbers() +void DeckLoader::clearSetNamesAndNumbers(const DeckList *deckList) { auto clearSetNameAndNumber = [](const auto node, auto card) { Q_UNUSED(node) @@ -419,19 +427,22 @@ DeckLoader::FileFormat DeckLoader::getFormatFromName(const QString &fileName) return PlainTextFormat; } -void DeckLoader::saveToClipboard(bool addComments, bool addSetNameAndNumber) const +void DeckLoader::saveToClipboard(const DeckList *deckList, bool addComments, bool addSetNameAndNumber) { QString buffer; QTextStream stream(&buffer); - saveToStream_Plain(stream, addComments, addSetNameAndNumber); + saveToStream_Plain(stream, deckList, addComments, addSetNameAndNumber); QApplication::clipboard()->setText(buffer, QClipboard::Clipboard); QApplication::clipboard()->setText(buffer, QClipboard::Selection); } -bool DeckLoader::saveToStream_Plain(QTextStream &out, bool addComments, bool addSetNameAndNumber) const +bool DeckLoader::saveToStream_Plain(QTextStream &out, + const DeckList *deckList, + bool addComments, + bool addSetNameAndNumber) { if (addComments) { - saveToStream_DeckHeader(out); + saveToStream_DeckHeader(out, deckList); } // loop zones @@ -447,7 +458,7 @@ bool DeckLoader::saveToStream_Plain(QTextStream &out, bool addComments, bool add return true; } -void DeckLoader::saveToStream_DeckHeader(QTextStream &out) const +void DeckLoader::saveToStream_DeckHeader(QTextStream &out, const DeckList *deckList) { if (!deckList->getName().isEmpty()) { out << "// " << deckList->getName() << "\n\n"; @@ -465,7 +476,7 @@ void DeckLoader::saveToStream_DeckHeader(QTextStream &out) const void DeckLoader::saveToStream_DeckZone(QTextStream &out, const InnerDecklistNode *zoneNode, bool addComments, - bool addSetNameAndNumber) const + bool addSetNameAndNumber) { // group cards by card type and count the subtotals QMultiMap cardsByType; @@ -513,7 +524,7 @@ void DeckLoader::saveToStream_DeckZoneCards(QTextStream &out, const InnerDecklistNode *zoneNode, QList cards, bool addComments, - bool addSetNameAndNumber) const + bool addSetNameAndNumber) { // QMultiMap sorts values in reverse order for (int i = cards.size() - 1; i >= 0; --i) { @@ -589,7 +600,7 @@ bool DeckLoader::convertToCockatriceFormat(QString fileName) return result; } -QString DeckLoader::getCardZoneFromName(QString cardName, QString currentZoneName) +QString DeckLoader::getCardZoneFromName(const QString &cardName, QString currentZoneName) { CardInfoPtr card = CardDatabaseManager::query()->getCardInfo(cardName); @@ -600,7 +611,7 @@ QString DeckLoader::getCardZoneFromName(QString cardName, QString currentZoneNam return currentZoneName; } -QString DeckLoader::getCompleteCardName(const QString &cardName) const +QString DeckLoader::getCompleteCardName(const QString &cardName) { if (CardDatabaseManager::getInstance()) { ExactCard temp = CardDatabaseManager::query()->guessCard({cardName}); @@ -672,7 +683,7 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, InnerDecklistNode *node) cursor->movePosition(QTextCursor::End); } -void DeckLoader::printDeckList(QPrinter *printer) +void DeckLoader::printDeckList(QPrinter *printer, const DeckList *deckList) { QTextDocument doc; diff --git a/cockatrice/src/interface/deck_loader/deck_loader.h b/cockatrice/src/interface/deck_loader/deck_loader.h index c8d010f23..a83542ce9 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.h +++ b/cockatrice/src/interface/deck_loader/deck_loader.h @@ -21,13 +21,6 @@ signals: void deckLoaded(); void loadFinished(bool success); -public slots: - /** - * @brief Prints the decklist to the provided QPrinter. - * @param printer The printer to render the decklist to. - */ - void printDeckList(QPrinter *printer); - public: enum FileFormat { @@ -84,7 +77,7 @@ public: return getLastFileName().isEmpty() && getLastRemoteDeckId() == -1; } - void clearSetNamesAndNumbers(); + static void clearSetNamesAndNumbers(const DeckList *deckList); static FileFormat getFormatFromName(const QString &fileName); bool loadFromFile(const QString &fileName, FileFormat fmt, bool userRequest = false); @@ -92,15 +85,25 @@ public: bool loadFromRemote(const QString &nativeString, int remoteDeckId); bool saveToFile(const QString &fileName, FileFormat fmt); bool updateLastLoadedTimestamp(const QString &fileName, FileFormat fmt); - QString exportDeckToDecklist(DecklistWebsite website); - void setProviderIdToPreferredPrinting(); - void resolveSetNameAndNumberToProviderID(); + static QString exportDeckToDecklist(const DeckList *deckList, DecklistWebsite website); - void saveToClipboard(bool addComments = true, bool addSetNameAndNumber = true) const; + static void setProviderIdToPreferredPrinting(const DeckList *deckList); + static void resolveSetNameAndNumberToProviderID(const DeckList *deckList); + + static void saveToClipboard(const DeckList *deckList, bool addComments = true, bool addSetNameAndNumber = true); + static bool saveToStream_Plain(QTextStream &out, + const DeckList *deckList, + bool addComments = true, + bool addSetNameAndNumber = true); + + /** + * @brief Prints the decklist to the provided QPrinter. + * @param printer The printer to render the decklist to. + * @param deckList + */ + static void printDeckList(QPrinter *printer, const DeckList *deckList); - // overload - bool saveToStream_Plain(QTextStream &out, bool addComments = true, bool addSetNameAndNumber = true) const; bool convertToCockatriceFormat(QString fileName); DeckList *getDeckList() @@ -109,21 +112,21 @@ public: } private: - void printDeckListNode(QTextCursor *cursor, InnerDecklistNode *node); + static void printDeckListNode(QTextCursor *cursor, InnerDecklistNode *node); + static void saveToStream_DeckHeader(QTextStream &out, const DeckList *deckList); -protected: - void saveToStream_DeckHeader(QTextStream &out) const; - void saveToStream_DeckZone(QTextStream &out, - const InnerDecklistNode *zoneNode, - bool addComments = true, - bool addSetNameAndNumber = true) const; - void saveToStream_DeckZoneCards(QTextStream &out, - const InnerDecklistNode *zoneNode, - QList cards, - bool addComments = true, - bool addSetNameAndNumber = true) const; - [[nodiscard]] QString getCardZoneFromName(QString cardName, QString currentZoneName); - [[nodiscard]] QString getCompleteCardName(const QString &cardName) const; + static void saveToStream_DeckZone(QTextStream &out, + const InnerDecklistNode *zoneNode, + bool addComments = true, + bool addSetNameAndNumber = true); + static void saveToStream_DeckZoneCards(QTextStream &out, + const InnerDecklistNode *zoneNode, + QList cards, + bool addComments = true, + bool addSetNameAndNumber = true); + + [[nodiscard]] static QString getCardZoneFromName(const QString &cardName, QString currentZoneName); + [[nodiscard]] static QString getCompleteCardName(const QString &cardName); }; #endif 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 8ad86eadc..fc1b9ebe2 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_widget.cpp @@ -183,7 +183,7 @@ void CardInfoPictureWidget::paintEvent(QPaintEvent *event) QPixmap transformedPixmap = resizedPixmap; // Default pixmap if (SettingsCache::instance().getAutoRotateSidewaysLayoutCards()) { - if (exactCard.getInfo().getLandscapeOrientation()) { + if (exactCard.getInfo().getUiAttributes().landscapeOrientation) { // Rotate pixmap 90 degrees to the left QTransform transform; transform.rotate(90); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp index 2b1b63065..629b23787 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp @@ -102,15 +102,20 @@ void DlgCreateGame::sharedCtor() startingLifeTotalEdit->setValue(20); startingLifeTotalLabel->setBuddy(startingLifeTotalEdit); - shareDecklistsOnLoadLabel = new QLabel(tr("Open decklists in lobby")); - shareDecklistsOnLoadCheckBox = new QCheckBox(); - shareDecklistsOnLoadLabel->setBuddy(shareDecklistsOnLoadCheckBox); + shareDecklistsOnLoadCheckBox = new QCheckBox(tr("Open decklists in lobby")); + + createGameAsJudgeCheckBox = new QCheckBox(tr("Create game as judge")); auto *gameSetupOptionsLayout = new QGridLayout; gameSetupOptionsLayout->addWidget(startingLifeTotalLabel, 0, 0); gameSetupOptionsLayout->addWidget(startingLifeTotalEdit, 0, 1); - gameSetupOptionsLayout->addWidget(shareDecklistsOnLoadLabel, 1, 0); - gameSetupOptionsLayout->addWidget(shareDecklistsOnLoadCheckBox, 1, 1); + gameSetupOptionsLayout->addWidget(shareDecklistsOnLoadCheckBox, 1, 0); + if (room->getUserInfo()->user_level() & ServerInfo_User::IsJudge) { + gameSetupOptionsLayout->addWidget(createGameAsJudgeCheckBox, 2, 0); + } else { + createGameAsJudgeCheckBox->setChecked(false); + createGameAsJudgeCheckBox->setHidden(true); + } gameSetupOptionsGroupBox = new QGroupBox(tr("Game setup options")); gameSetupOptionsGroupBox->setLayout(gameSetupOptionsLayout); @@ -245,6 +250,7 @@ void DlgCreateGame::actReset() startingLifeTotalEdit->setValue(20); shareDecklistsOnLoadCheckBox->setChecked(false); + createGameAsJudgeCheckBox->setChecked(false); QMapIterator gameTypeCheckBoxIterator(gameTypeCheckBoxes); while (gameTypeCheckBoxIterator.hasNext()) { @@ -270,7 +276,7 @@ void DlgCreateGame::actOK() cmd.set_spectators_need_password(spectatorsNeedPasswordCheckBox->isChecked()); cmd.set_spectators_can_talk(spectatorsCanTalkCheckBox->isChecked()); cmd.set_spectators_see_everything(spectatorsSeeEverythingCheckBox->isChecked()); - cmd.set_join_as_judge(QApplication::keyboardModifiers() & Qt::ShiftModifier); + cmd.set_join_as_judge(createGameAsJudgeCheckBox->isChecked()); cmd.set_join_as_spectator(createGameAsSpectatorCheckBox->isChecked()); cmd.set_starting_life_total(startingLifeTotalEdit->value()); cmd.set_share_decklists_on_load(shareDecklistsOnLoadCheckBox->isChecked()); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_create_game.h b/cockatrice/src/interface/widgets/dialogs/dlg_create_game.h index 71359a758..e28363311 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_create_game.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_create_game.h @@ -46,7 +46,7 @@ private: QSpinBox *maxPlayersEdit, *startingLifeTotalEdit; QCheckBox *onlyBuddiesCheckBox, *onlyRegisteredCheckBox; QCheckBox *spectatorsAllowedCheckBox, *spectatorsNeedPasswordCheckBox, *spectatorsCanTalkCheckBox, - *spectatorsSeeEverythingCheckBox, *createGameAsSpectatorCheckBox; + *spectatorsSeeEverythingCheckBox, *createGameAsJudgeCheckBox, *createGameAsSpectatorCheckBox; QCheckBox *shareDecklistsOnLoadCheckBox; QDialogButtonBox *buttonBox; QPushButton *clearButton; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp index 479e3b887..a47a8221a 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp @@ -165,8 +165,8 @@ void DlgEditTokens::actAddToken() QString setName = CardSet::TOKENS_SETNAME; SetToPrintingsMap sets; sets[setName].append(PrintingInfo(databaseModel->getDatabase()->getSet(setName))); - CardInfoPtr card = CardInfo::newInstance(name, "", true, QVariantHash(), QList(), - QList(), sets, false, false, -1, false); + CardInfo::UiAttributes attributes = {.tableRow = -1}; + CardInfoPtr card = CardInfo::newInstance(name, "", true, {}, {}, {}, sets, attributes); card->setCardType("Token"); databaseModel->getDatabase()->addCard(card); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.cpp index 0c5191f99..1bf98822c 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.cpp @@ -37,7 +37,7 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes, hideIgnoredUserGames = new QCheckBox(tr("Hide 'ignored user' games")); hideIgnoredUserGames->setChecked(gamesProxyModel->getHideIgnoredUserGames()); - hideNotBuddyCreatedGames = new QCheckBox(tr("Hide games not created by buddy")); + hideNotBuddyCreatedGames = new QCheckBox(tr("Hide games not created by buddies")); hideNotBuddyCreatedGames->setChecked(gamesProxyModel->getHideNotBuddyCreatedGames()); hideOpenDecklistGames = new QCheckBox(tr("Hide games with forced open decklists")); @@ -56,7 +56,7 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes, auto *gameNameFilterLabel = new QLabel(tr("Game &description:")); gameNameFilterLabel->setBuddy(gameNameFilterEdit); creatorNameFilterEdit = new QLineEdit; - creatorNameFilterEdit->setText(gamesProxyModel->getCreatorNameFilter()); + creatorNameFilterEdit->setText(gamesProxyModel->getCreatorNameFilters().join(", ")); auto *creatorNameFilterLabel = new QLabel(tr("&Creator name:")); creatorNameFilterLabel->setBuddy(creatorNameFilterEdit); @@ -232,9 +232,9 @@ QString DlgFilterGames::getGameNameFilter() const return gameNameFilterEdit->text(); } -QString DlgFilterGames::getCreatorNameFilter() const +QStringList DlgFilterGames::getCreatorNameFilters() const { - return creatorNameFilterEdit->text(); + return creatorNameFilterEdit->text().split(",", Qt::SkipEmptyParts); } QSet DlgFilterGames::getGameTypeFilter() const diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.h b/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.h index ab07a02d4..37b208749 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.h @@ -71,7 +71,7 @@ public: bool getHideNotBuddyCreatedGames() const; QString getGameNameFilter() const; void setGameNameFilter(const QString &_gameNameFilter); - QString getCreatorNameFilter() const; + QStringList getCreatorNameFilters() const; void setCreatorNameFilter(const QString &_creatorNameFilter); QSet getGameTypeFilter() const; void setGameTypeFilter(const QSet &_gameTypeFilter); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp index b1f4d066f..3385dd41e 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp @@ -82,9 +82,9 @@ bool AbstractDlgDeckTextEdit::loadIntoDeck(DeckLoader *deckLoader) const if (deckLoader->getDeckList()->loadFromStream_Plain(stream, true)) { if (loadSetNameAndNumberCheckBox->isChecked()) { - deckLoader->resolveSetNameAndNumberToProviderID(); + DeckLoader::resolveSetNameAndNumberToProviderID(deckLoader->getDeckList()); } else { - deckLoader->clearSetNamesAndNumbers(); + DeckLoader::clearSetNamesAndNumbers(deckLoader->getDeckList()); } return true; } @@ -154,17 +154,17 @@ DlgEditDeckInClipboard::DlgEditDeckInClipboard(const DeckLoader &deckList, bool * @param addComments Whether to add annotations * @return A QString */ -static QString deckListToString(const DeckLoader *deckList, bool addComments) +static QString deckListToString(const DeckList *deckList, bool addComments) { QString buffer; QTextStream stream(&buffer); - deckList->saveToStream_Plain(stream, addComments); + DeckLoader::saveToStream_Plain(stream, deckList, addComments); return buffer; } void DlgEditDeckInClipboard::actRefresh() { - setText(deckListToString(deckLoader, annotated)); + setText(deckListToString(deckLoader->getDeckList(), annotated)); } void DlgEditDeckInClipboard::actOK() diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp index 5b3e44877..f29984ab0 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp @@ -98,7 +98,7 @@ void DlgLoadDeckFromWebsite::accept() DeckLoader *loader = new DeckLoader(this); QTextStream stream(&deckText); loader->getDeckList()->loadFromStream_Plain(stream, false); - loader->resolveSetNameAndNumberToProviderID(); + DeckLoader::resolveSetNameAndNumberToProviderID(loader->getDeckList()); deck = loader; QDialog::accept(); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp index 3bb81844c..121ce4c86 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp @@ -162,14 +162,14 @@ void DlgSelectSetForCards::actOK() void DlgSelectSetForCards::actClear() { - qobject_cast(model->getDeckList())->clearSetNamesAndNumbers(); + DeckLoader::clearSetNamesAndNumbers(model->getDeckList()); accept(); } void DlgSelectSetForCards::actSetAllToPreferred() { - qobject_cast(model->getDeckList())->clearSetNamesAndNumbers(); - qobject_cast(model->getDeckList())->setProviderIdToPreferredPrinting(); + DeckLoader::clearSetNamesAndNumbers(model->getDeckList()); + DeckLoader::setProviderIdToPreferredPrinting(model->getDeckList()); accept(); } diff --git a/cockatrice/src/interface/widgets/general/home_widget.cpp b/cockatrice/src/interface/widgets/general/home_widget.cpp index 59237cc47..cbf53ee4f 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.cpp +++ b/cockatrice/src/interface/widgets/general/home_widget.cpp @@ -178,7 +178,7 @@ QGroupBox *HomeWidget::createButtons() boxLayout->addSpacing(25); connectButton = new HomeStyledButton("Connect/Play", gradientColors); - boxLayout->addWidget(connectButton, 1); + boxLayout->addWidget(connectButton); auto visualDeckEditorButton = new HomeStyledButton(tr("Create New Deck"), gradientColors); connect(visualDeckEditorButton, &QPushButton::clicked, tabSupervisor, diff --git a/cockatrice/src/interface/widgets/server/game_selector.cpp b/cockatrice/src/interface/widgets/server/game_selector.cpp index 8aa3211bd..f4366d8ab 100644 --- a/cockatrice/src/interface/widgets/server/game_selector.cpp +++ b/cockatrice/src/interface/widgets/server/game_selector.cpp @@ -76,6 +76,12 @@ GameSelector::GameSelector(AbstractClient *_client, gameListView->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + if (showFilters && restoresettings) { + quickFilterToolBar = new GameSelectorQuickFilterToolBar(this, tabSupervisor, gameListProxyModel, gameTypeMap); + } else { + quickFilterToolBar = nullptr; + } + filterButton = new QPushButton; filterButton->setIcon(QPixmap("theme:icons/search")); connect(filterButton, &QPushButton::clicked, this, &GameSelector::actSetFilter); @@ -92,7 +98,9 @@ GameSelector::GameSelector(AbstractClient *_client, createButton = nullptr; } joinButton = new QPushButton; + joinAsJudgeButton = new QPushButton; spectateButton = new QPushButton; + joinAsJudgeSpectatorButton = new QPushButton; QHBoxLayout *buttonLayout = new QHBoxLayout; if (showFilters) { @@ -103,10 +111,23 @@ GameSelector::GameSelector(AbstractClient *_client, if (room) buttonLayout->addWidget(createButton); buttonLayout->addWidget(joinButton); + if (tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsJudge) { + buttonLayout->addWidget(joinAsJudgeButton); + } else { + joinAsJudgeButton->setHidden(true); + } buttonLayout->addWidget(spectateButton); + if (tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsJudge) { + buttonLayout->addWidget(joinAsJudgeSpectatorButton); + } else { + joinAsJudgeSpectatorButton->setHidden(true); + } buttonLayout->setAlignment(Qt::AlignTop); QVBoxLayout *mainLayout = new QVBoxLayout; + if (showFilters && restoresettings) { + mainLayout->addWidget(quickFilterToolBar); + } mainLayout->addWidget(gameListView); mainLayout->addLayout(buttonLayout); @@ -117,7 +138,9 @@ GameSelector::GameSelector(AbstractClient *_client, setMinimumHeight(200); connect(joinButton, &QPushButton::clicked, this, &GameSelector::actJoin); - connect(spectateButton, &QPushButton::clicked, this, &GameSelector::actSpectate); + connect(joinAsJudgeButton, &QPushButton::clicked, this, &GameSelector::actJoinAsJudge); + connect(spectateButton, &QPushButton::clicked, this, &GameSelector::actJoinAsSpectator); + connect(joinAsJudgeSpectatorButton, &QPushButton::clicked, this, &GameSelector::actJoinAsJudgeSpectator); connect(gameListView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &GameSelector::actSelectedGameChanged); connect(gameListView, &QTreeView::activated, this, &GameSelector::actJoin); @@ -158,7 +181,7 @@ void GameSelector::actSetFilter() gameListProxyModel->setGameFilters( dlg.getHideBuddiesOnlyGames(), dlg.getHideIgnoredUserGames(), dlg.getHideFullGames(), dlg.getHideGamesThatStarted(), dlg.getHidePasswordProtectedGames(), dlg.getHideNotBuddyCreatedGames(), - dlg.getHideOpenDecklistGames(), dlg.getGameNameFilter(), dlg.getCreatorNameFilter(), dlg.getGameTypeFilter(), + dlg.getHideOpenDecklistGames(), dlg.getGameNameFilter(), dlg.getCreatorNameFilters(), dlg.getGameTypeFilter(), dlg.getMaxPlayersFilterMin(), dlg.getMaxPlayersFilterMax(), dlg.getMaxGameAge(), dlg.getShowOnlyIfSpectatorsCanWatch(), dlg.getShowSpectatorPasswordProtected(), dlg.getShowOnlyIfSpectatorsCanChat(), dlg.getShowOnlyIfSpectatorsCanSeeHands()); @@ -238,12 +261,30 @@ void GameSelector::checkResponse(const Response &response) void GameSelector::actJoin() { - return joinGame(false); + joinGame(); } -void GameSelector::actSpectate() +void GameSelector::actJoinAsJudge() { - return joinGame(true); + if (!(tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsJudge)) { + joinGame(); + } else { + joinGame(false, true); + } +} + +void GameSelector::actJoinAsSpectator() +{ + joinGame(true); +} + +void GameSelector::actJoinAsJudgeSpectator() +{ + if (!(tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsJudge)) { + joinGame(true); + } else { + joinGame(true, true); + } } void GameSelector::customContextMenu(const QPoint &point) @@ -257,7 +298,7 @@ void GameSelector::customContextMenu(const QPoint &point) connect(&joinGame, &QAction::triggered, this, &GameSelector::actJoin); QAction spectateGame(tr("Spectate Game")); - connect(&spectateGame, &QAction::triggered, this, &GameSelector::actSpectate); + connect(&spectateGame, &QAction::triggered, this, &GameSelector::actJoinAsSpectator); QAction getGameInfo(tr("Game Information")); connect(&getGameInfo, &QAction::triggered, this, [=, this]() { @@ -270,12 +311,25 @@ void GameSelector::customContextMenu(const QPoint &point) QMenu menu; menu.addAction(&joinGame); + + if (tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsJudge) { + QAction joinGameAsJudge(tr("Join Game as Judge")); + connect(&joinGameAsJudge, &QAction::triggered, this, &GameSelector::actJoinAsJudge); + + menu.addAction(&joinGameAsJudge); + + QAction spectateGameAsJudge(tr("Spectate Game as Judge")); + connect(&spectateGameAsJudge, &QAction::triggered, this, &GameSelector::actJoinAsJudgeSpectator); + + menu.addAction(&spectateGameAsJudge); + } + menu.addAction(&spectateGame); menu.addAction(&getGameInfo); menu.exec(gameListView->mapToGlobal(point)); } -void GameSelector::joinGame(const bool isSpectator) +void GameSelector::joinGame(const bool asSpectator, const bool asJudge) { QModelIndex ind = gameListView->currentIndex(); if (!ind.isValid()) { @@ -287,7 +341,7 @@ void GameSelector::joinGame(const bool isSpectator) return; } - bool spectator = isSpectator || game.player_count() == game.max_players(); + bool spectator = asSpectator || game.player_count() == game.max_players(); bool overrideRestrictions = !tabSupervisor->getAdminLocked(); QString password; @@ -304,7 +358,7 @@ void GameSelector::joinGame(const bool isSpectator) cmd.set_password(password.toStdString()); cmd.set_spectator(spectator); cmd.set_override_restrictions(overrideRestrictions); - cmd.set_join_as_judge((QApplication::keyboardModifiers() & Qt::ShiftModifier) != 0); + cmd.set_join_as_judge(asJudge); TabRoom *r = tabSupervisor->getRoomTabs().value(game.room_id()); if (!r) { @@ -356,7 +410,9 @@ void GameSelector::retranslateUi() if (createButton) createButton->setText(tr("C&reate")); joinButton->setText(tr("&Join")); + joinAsJudgeButton->setText(tr("Join as judge")); spectateButton->setText(tr("J&oin as spectator")); + joinAsJudgeSpectatorButton->setText(tr("Join as judge spectator")); updateTitle(); } diff --git a/cockatrice/src/interface/widgets/server/game_selector.h b/cockatrice/src/interface/widgets/server/game_selector.h index 0ca477fa9..ea0a4feb0 100644 --- a/cockatrice/src/interface/widgets/server/game_selector.h +++ b/cockatrice/src/interface/widgets/server/game_selector.h @@ -1,12 +1,7 @@ -/** - * @file game_selector.h - * @ingroup Lobby - * @brief TODO: Document this. - */ - #ifndef GAMESELECTOR_H #define GAMESELECTOR_H +#include "game_selector_quick_filter_toolbar.h" #include "game_type_map.h" #include @@ -26,46 +21,167 @@ class TabRoom; class ServerInfo_Game; class Response; +/** + * @class GameSelector + * @ingroup Lobby + * @brief Provides a widget for displaying, filtering, joining, spectating, and creating games in a room. + * + * The GameSelector displays all available games in a QTreeView. It supports filtering, + * creating, joining, spectating, and viewing game details. Integrates with TabSupervisor + * and TabRoom for room and game management. + */ class GameSelector : public QGroupBox { Q_OBJECT private slots: + /** + * @brief Opens a dialog to set filters for the game list. + * + * Updates the proxy model with selected filter parameters and refreshes the displayed game list. + */ void actSetFilter(); + + /** + * @brief Clears all filters applied to the game list. + * + * Resets the proxy model to show all games. + */ void actClearFilter(); + + /** + * @brief Opens the dialog to create a new game in the current room. + */ void actCreate(); + /** + * @brief Joins the currently selected game as a player. + */ void actJoin(); - void actSpectate(); + /** + * @brief Joins the currently selected game as a judge. + */ + void actJoinAsJudge(); + + /** + * @brief Joins the currently selected game as a spectator. + */ + void actJoinAsSpectator(); + void actJoinAsJudgeSpectator(); + + /** + * @brief Shows the custom context menu for a game when right-clicked. + * @param point The point at which the context menu is requested. + */ void customContextMenu(const QPoint &point); + /** + * @brief Slot called when the selected game changes. + * @param current The currently selected index. + * @param previous The previously selected index. + * + * Updates the enabled/disabled state of buttons depending on the selected game. + */ void actSelectedGameChanged(const QModelIndex ¤t, const QModelIndex &previous); + + /** + * @brief Processes server responses for join or spectate commands. + * @param response The response from the server. + * + * Displays error messages for failed join/spectate attempts. + */ void checkResponse(const Response &response); + /** + * @brief Refreshes the game list when the ignore list is received from the server. + * @param _ignoreList The list of users being ignored. + */ void ignoreListReceived(const QList &_ignoreList); + + /** + * @brief Processes events where a user is added to a list (e.g., ignore or buddy). + * @param event The event information. + */ void processAddToListEvent(const Event_AddToList &event); + + /** + * @brief Processes events where a user is removed from a list (e.g., ignore or buddy). + * @param event The event information. + */ void processRemoveFromListEvent(const Event_RemoveFromList &event); + signals: + /** + * @brief Emitted when a game has been successfully joined. + * @param gameId The ID of the joined game. + */ void gameJoined(int gameId); private: - AbstractClient *client; - TabSupervisor *tabSupervisor; - TabRoom *room; + AbstractClient *client; /**< The network client used to communicate with the server. */ + TabSupervisor *tabSupervisor; /**< Reference to TabSupervisor for managing tabs and rooms. */ + TabRoom *room; /**< The current room. */ - QTreeView *gameListView; - GamesModel *gameListModel; - GamesProxyModel *gameListProxyModel; - QPushButton *filterButton, *clearFilterButton, *createButton, *joinButton, *spectateButton; - const bool showFilters; - GameTypeMap gameTypeMap; + QTreeView *gameListView; /**< View widget for displaying the game list. */ + GamesModel *gameListModel; /**< Model containing all games. */ + GamesProxyModel *gameListProxyModel; /**< Proxy model for filtering and sorting the game list. */ + GameSelectorQuickFilterToolBar *quickFilterToolBar; + + QPushButton *filterButton; /**< Button to open the filter dialog. */ + QPushButton *clearFilterButton; /**< Button to clear active filters. */ + QPushButton *createButton; /**< Button to create a new game (only if room is set). */ + QPushButton *joinButton; /**< Button to join the selected game. */ + QPushButton *joinAsJudgeButton; /**< Button to join the selected game as a judge. */ + QPushButton *spectateButton; /**< Button to spectate the selected game. */ + QPushButton *joinAsJudgeSpectatorButton; /**< Button to join the selected game as a spectating judge. */ + + const bool showFilters; /**< Determines whether filter buttons are displayed. */ + GameTypeMap gameTypeMap; /**< Mapping of game types for the current room. */ + + /** + * @brief Updates the widget title to reflect the current number of displayed games. + * + * Shows the number of visible games versus total games if filters are enabled. + */ void updateTitle(); + + /** + * @brief Disables create/join/spectate buttons. + */ void disableButtons(); + + /** + * @brief Enables buttons for the currently selected game. + */ void enableButtons(); + + /** + * @brief Enables buttons for a specific game index. + * @param current The index of the currently selected game. + */ void enableButtonsForIndex(const QModelIndex ¤t); - void joinGame(const bool isSpectator); + + /** + * @brief Performs the join or spectate action for the currently selected game. + * @param asSpectator True to join as a spectator, false to join as a player. + * @param asJudge True to join as a judge, false to join as a player. + * + * Handles password prompts, overrides, and sending the join command to the server. + */ + void joinGame(bool asSpectator = false, bool asJudge = false); public: + /** + * @brief Constructs a GameSelector widget. + * @param _client The network client used to communicate with the server. + * @param _tabSupervisor Reference to TabSupervisor for managing tabs and rooms. + * @param _room Pointer to the current room; nullptr if no room is selected. + * @param _rooms Map of room IDs to room names. + * @param _gameTypes Map of room IDs to their available game types. + * @param restoresettings Whether to restore filter settings from previous sessions. + * @param _showfilters Whether to display filter buttons. + * @param parent Parent QWidget. + */ GameSelector(AbstractClient *_client, TabSupervisor *_tabSupervisor, TabRoom *_room, @@ -74,7 +190,16 @@ public: const bool restoresettings, const bool _showfilters, QWidget *parent = nullptr); + + /** + * @brief Updates UI text for translation/localization. + */ void retranslateUi(); + + /** + * @brief Updates or adds a game entry in the list. + * @param info The ServerInfo_Game object containing information about the game to update. + */ void processGameInfo(const ServerInfo_Game &info); }; diff --git a/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp b/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp new file mode 100644 index 000000000..c63b52450 --- /dev/null +++ b/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp @@ -0,0 +1,110 @@ +#include "game_selector_quick_filter_toolbar.h" + +#include "games_model.h" +#include "user/user_list_manager.h" + +#include +#include +#include +#include +#include + +GameSelectorQuickFilterToolBar::GameSelectorQuickFilterToolBar(QWidget *parent, + TabSupervisor *_tabSupervisor, + GamesProxyModel *_model, + const QMap &allGameTypes) + : QWidget(parent), tabSupervisor(_tabSupervisor), model(_model) +{ + mainLayout = new QHBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(5); + + searchBar = new QLineEdit(this); + searchBar->setText(model->getCreatorNameFilters().join(", ")); + connect(searchBar, &QLineEdit::textChanged, this, [this](const QString &text) { model->setGameNameFilter(text); }); + + hideGamesNotCreatedByBuddiesCheckBox = new QCheckBox(this); + hideGamesNotCreatedByBuddiesCheckBox->setChecked(model->getHideBuddiesOnlyGames()); + connect(hideGamesNotCreatedByBuddiesCheckBox, &QCheckBox::toggled, this, [this](bool checked) { + if (checked) { + QStringList buddyNames; + for (auto buddy : tabSupervisor->getUserListManager()->getBuddyList().values()) { + buddyNames << QString::fromStdString(buddy.name()); + } + model->setCreatorNameFilters(buddyNames); + } else { + model->setCreatorNameFilters({}); + } + }); + + hideFullGamesCheckBox = new QCheckBox(this); + hideFullGamesCheckBox->setChecked(model->getHideFullGames()); + connect(hideFullGamesCheckBox, &QCheckBox::toggled, this, + [this](bool checked) { model->setHideFullGames(checked); }); + + hideStartedGamesCheckBox = new QCheckBox(this); + hideStartedGamesCheckBox->setChecked(model->getHideGamesThatStarted()); + connect(hideStartedGamesCheckBox, &QCheckBox::toggled, this, + [this](bool checked) { model->setHideGamesThatStarted(checked); }); + + filterToFormatComboBox = new QComboBox(this); + + // Add a "No filter" / "All types" option first + filterToFormatComboBox->addItem(tr("All types"), QVariant()); // empty QVariant = no filter + + QMapIterator i(allGameTypes); + while (i.hasNext()) { + i.next(); + filterToFormatComboBox->addItem(i.value(), i.key()); // text = name, data = type ID + } + + QSet currentTypes = model->getGameTypeFilter(); + if (currentTypes.size() == 1) { + int typeId = *currentTypes.begin(); + int index = filterToFormatComboBox->findData(typeId); + if (index >= 0) + filterToFormatComboBox->setCurrentIndex(index); + } else { + filterToFormatComboBox->setCurrentIndex(0); // "All types" by default + } + + // Update proxy model on selection change + connect(filterToFormatComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) { + QVariant data = filterToFormatComboBox->itemData(index); + if (!data.isValid()) { + model->setGameTypeFilter({}); // empty = no filter + } else { + int typeId = data.toInt(); + model->setGameTypeFilter({typeId}); + } + }); + + hideGamesNotCreatedByBuddiesCheckBox->setMinimumSize(20, 20); + hideFullGamesCheckBox->setMinimumSize(20, 20); + hideStartedGamesCheckBox->setMinimumSize(20, 20); + +#if defined(Q_OS_MAC) + mainLayout->setSpacing(6); +#endif + + mainLayout->addWidget(searchBar); + mainLayout->addWidget(filterToFormatComboBox); + mainLayout->addWidget(hideGamesNotCreatedByBuddiesCheckBox); + mainLayout->addSpacing(5); + mainLayout->addWidget(hideFullGamesCheckBox); + mainLayout->addSpacing(5); + mainLayout->addWidget(hideStartedGamesCheckBox); + + setLayout(mainLayout); + + retranslateUi(); +} + +void GameSelectorQuickFilterToolBar::retranslateUi() +{ + searchBar->setPlaceholderText(tr("Filter by game name...")); + filterToFormatComboBox->setToolTip(tr("Filter by game type/format")); + hideGamesNotCreatedByBuddiesCheckBox->setText(tr("Hide games not created by buddies")); + hideFullGamesCheckBox->setText(tr("Hide full games")); + hideStartedGamesCheckBox->setText(tr("Hide started games")); +} diff --git a/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.h b/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.h new file mode 100644 index 000000000..f7e0c6fb1 --- /dev/null +++ b/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.h @@ -0,0 +1,39 @@ +#ifndef COCKATRICE_GAME_SELECTOR_QUICK_FILTER_TOOLBAR_H +#define COCKATRICE_GAME_SELECTOR_QUICK_FILTER_TOOLBAR_H + +#include "../tabs/tab_supervisor.h" +#include "games_model.h" + +#include +#include +#include +#include +#include +#include + +class GameSelectorQuickFilterToolBar : public QWidget +{ + + Q_OBJECT + +public: + explicit GameSelectorQuickFilterToolBar(QWidget *parent, + TabSupervisor *tabSupervisor, + GamesProxyModel *model, + const QMap &allGameTypes); + void retranslateUi(); + +private: + TabSupervisor *tabSupervisor; + GamesProxyModel *model; + + QHBoxLayout *mainLayout; + + QLineEdit *searchBar; + QCheckBox *hideGamesNotCreatedByBuddiesCheckBox; + QCheckBox *hideFullGamesCheckBox; + QCheckBox *hideStartedGamesCheckBox; + QComboBox *filterToFormatComboBox; +}; + +#endif // COCKATRICE_GAME_SELECTOR_QUICK_FILTER_TOOLBAR_H diff --git a/cockatrice/src/interface/widgets/server/games_model.cpp b/cockatrice/src/interface/widgets/server/games_model.cpp index 4c68390c9..dfb41fd29 100644 --- a/cockatrice/src/interface/widgets/server/games_model.cpp +++ b/cockatrice/src/interface/widgets/server/games_model.cpp @@ -294,7 +294,7 @@ void GamesProxyModel::setGameFilters(bool _hideBuddiesOnlyGames, bool _hideNotBuddyCreatedGames, bool _hideOpenDecklistGames, const QString &_gameNameFilter, - const QString &_creatorNameFilter, + const QStringList &_creatorNameFilters, const QSet &_gameTypeFilter, int _maxPlayersFilterMin, int _maxPlayersFilterMax, @@ -315,7 +315,7 @@ void GamesProxyModel::setGameFilters(bool _hideBuddiesOnlyGames, hideNotBuddyCreatedGames = _hideNotBuddyCreatedGames; hideOpenDecklistGames = _hideOpenDecklistGames; gameNameFilter = _gameNameFilter; - creatorNameFilter = _creatorNameFilter; + creatorNameFilters = _creatorNameFilters; gameTypeFilter = _gameTypeFilter; maxPlayersFilterMin = _maxPlayersFilterMin; maxPlayersFilterMax = _maxPlayersFilterMax; @@ -348,15 +348,15 @@ int GamesProxyModel::getNumFilteredGames() const void GamesProxyModel::resetFilterParameters() { - setGameFilters(false, false, false, false, false, false, false, QString(), QString(), {}, DEFAULT_MAX_PLAYERS_MIN, - DEFAULT_MAX_PLAYERS_MAX, DEFAULT_MAX_GAME_AGE, false, false, false, false); + setGameFilters(false, false, false, false, false, false, false, QString(), QStringList(), {}, + DEFAULT_MAX_PLAYERS_MIN, DEFAULT_MAX_PLAYERS_MAX, DEFAULT_MAX_GAME_AGE, false, false, false, false); } bool GamesProxyModel::areFilterParametersSetToDefaults() const { return !hideFullGames && !hideGamesThatStarted && !hidePasswordProtectedGames && !hideBuddiesOnlyGames && !hideOpenDecklistGames && !hideIgnoredUserGames && !hideNotBuddyCreatedGames && gameNameFilter.isEmpty() && - creatorNameFilter.isEmpty() && gameTypeFilter.isEmpty() && maxPlayersFilterMin == DEFAULT_MAX_PLAYERS_MIN && + creatorNameFilters.isEmpty() && gameTypeFilter.isEmpty() && maxPlayersFilterMin == DEFAULT_MAX_PLAYERS_MIN && maxPlayersFilterMax == DEFAULT_MAX_PLAYERS_MAX && maxGameAge == DEFAULT_MAX_GAME_AGE && !showOnlyIfSpectatorsCanWatch && !showSpectatorPasswordProtected && !showOnlyIfSpectatorsCanChat && !showOnlyIfSpectatorsCanSeeHands; @@ -379,7 +379,7 @@ void GamesProxyModel::loadFilterParameters(const QMap &allGameType gameFilters.isHideFullGames(), gameFilters.isHideGamesThatStarted(), gameFilters.isHidePasswordProtectedGames(), gameFilters.isHideNotBuddyCreatedGames(), gameFilters.isHideOpenDecklistGames(), gameFilters.getGameNameFilter(), - gameFilters.getCreatorNameFilter(), newGameTypeFilter, gameFilters.getMinPlayers(), + gameFilters.getCreatorNameFilters(), newGameTypeFilter, gameFilters.getMinPlayers(), gameFilters.getMaxPlayers(), gameFilters.getMaxGameAge(), gameFilters.isShowOnlyIfSpectatorsCanWatch(), gameFilters.isShowSpectatorPasswordProtected(), gameFilters.isShowOnlyIfSpectatorsCanChat(), gameFilters.isShowOnlyIfSpectatorsCanSeeHands()); @@ -396,7 +396,7 @@ void GamesProxyModel::saveFilterParameters(const QMap &allGameType gameFilters.setHideNotBuddyCreatedGames(hideNotBuddyCreatedGames); gameFilters.setHideOpenDecklistGames(hideOpenDecklistGames); gameFilters.setGameNameFilter(gameNameFilter); - gameFilters.setCreatorNameFilter(creatorNameFilter); + gameFilters.setCreatorNameFilters(creatorNameFilters); QMapIterator gameTypeIterator(allGameTypes); while (gameTypeIterator.hasNext()) { @@ -459,9 +459,17 @@ bool GamesProxyModel::filterAcceptsRow(int sourceRow) const if (!gameNameFilter.isEmpty()) if (!QString::fromStdString(game.description()).contains(gameNameFilter, Qt::CaseInsensitive)) return false; - if (!creatorNameFilter.isEmpty()) - if (!QString::fromStdString(game.creator_info().name()).contains(creatorNameFilter, Qt::CaseInsensitive)) + if (!creatorNameFilters.isEmpty()) { + bool found = false; + for (const auto &createNameFilter : creatorNameFilters) { + if (QString::fromStdString(game.creator_info().name()).contains(createNameFilter, Qt::CaseInsensitive)) { + found = true; + } + } + if (!found) { return false; + } + } QSet gameTypes; for (int i = 0; i < game.game_types_size(); ++i) diff --git a/cockatrice/src/interface/widgets/server/games_model.h b/cockatrice/src/interface/widgets/server/games_model.h index d29800f66..c704befcc 100644 --- a/cockatrice/src/interface/widgets/server/games_model.h +++ b/cockatrice/src/interface/widgets/server/games_model.h @@ -1,9 +1,3 @@ -/** - * @file games_model.h - * @ingroup Lobby - * @brief TODO: Document this. - */ - #ifndef GAMESMODEL_H #define GAMESMODEL_H @@ -19,60 +13,107 @@ class UserListProxy; +/** + * @class GamesModel + * @ingroup Lobby + * @brief Model storing all available games for display in a QTreeView or QTableView. + * + * Provides access to game information, supports sorting by different columns, + * and updates when new game data is received from the server. + */ class GamesModel : public QAbstractTableModel { Q_OBJECT private: - QList gameList; - QMap rooms; - QMap gameTypes; + QList gameList; /**< List of games currently displayed. */ + QMap rooms; /**< Map of room IDs to room names. */ + QMap gameTypes; /**< Map of room IDs to available game types. */ - static const int NUM_COLS = 8; + static const int NUM_COLS = 8; /**< Number of columns in the table. */ public: - static const int SORT_ROLE = Qt::UserRole + 1; + static const int SORT_ROLE = Qt::UserRole + 1; /**< Role used for sorting. */ + /** + * @brief Constructs a GamesModel. + * @param _rooms Mapping of room IDs to room names. + * @param _gameTypes Mapping of room IDs to their available game types. + * @param parent Parent QObject. + */ GamesModel(const QMap &_rooms, const QMap &_gameTypes, QObject *parent = nullptr); + int rowCount(const QModelIndex &parent = QModelIndex()) const override { return parent.isValid() ? 0 : gameList.size(); } + int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override { return NUM_COLS; } + QVariant data(const QModelIndex &index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + /** + * @brief Formats the game creation time into a human-readable string. + * @param secs Number of seconds since the game started. + * @return Short string representing game age (e.g., "new", ">5 min", ">2 hr"). + */ static const QString getGameCreatedString(const int secs); + + /** + * @brief Returns a reference to a specific game by row index. + * @param row Row index in the table. + * @return Reference to the ServerInfo_Game at the given row. + */ const ServerInfo_Game &getGame(int row); /** - * Update game list with a (possibly new) game. + * @brief Updates the game list with a new or updated game. + * @param game The ServerInfo_Game object to add or update. */ void updateGameList(const ServerInfo_Game &game); + /** + * @brief Returns the index of the room column. + */ int roomColIndex() { return 0; } + + /** + * @brief Returns the index of the start time column. + */ int startTimeColIndex() { return 1; } + /** + * @brief Returns the map of game types per room. + */ const QMap &getGameTypes() { return gameTypes; } }; -class ServerInfo_User; - +/** + * @class GamesProxyModel + * @ingroup Lobby + * @brief Proxy model for filtering and sorting the GamesModel based on user preferences. + * + * Supports filtering games based on buddies-only, ignored users, password protection, + * game types, creator, age, player count, and spectator permissions. + */ class GamesProxyModel : public QSortFilterProxyModel { Q_OBJECT private: - const UserListProxy *userListProxy; + const UserListProxy *userListProxy; /**< Proxy for checking user ignore/buddy lists. */ // If adding any additional filters, make sure to update: // - GamesProxyModel() @@ -88,16 +129,25 @@ private: bool hidePasswordProtectedGames; bool hideNotBuddyCreatedGames; bool hideOpenDecklistGames; - QString gameNameFilter, creatorNameFilter; + QString gameNameFilter; + QStringList creatorNameFilters; QSet gameTypeFilter; quint32 maxPlayersFilterMin, maxPlayersFilterMax; QTime maxGameAge; - bool showOnlyIfSpectatorsCanWatch, showSpectatorPasswordProtected, showOnlyIfSpectatorsCanChat, - showOnlyIfSpectatorsCanSeeHands; + bool showOnlyIfSpectatorsCanWatch; + bool showSpectatorPasswordProtected; + bool showOnlyIfSpectatorsCanChat; + bool showOnlyIfSpectatorsCanSeeHands; public: + /** + * @brief Constructs a GamesProxyModel. + * @param parent Parent QObject. + * @param _userListProxy Proxy for accessing ignore/buddy lists. + */ explicit GamesProxyModel(QObject *parent = nullptr, const UserListProxy *_userListProxy = nullptr); + // Getters for filter parameters bool getHideBuddiesOnlyGames() const { return hideBuddiesOnlyGames; @@ -130,9 +180,9 @@ public: { return gameNameFilter; } - QString getCreatorNameFilter() const + QStringList getCreatorNameFilters() const { - return creatorNameFilter; + return creatorNameFilters; } QSet getGameTypeFilter() const { @@ -166,6 +216,10 @@ public: { return showOnlyIfSpectatorsCanSeeHands; } + + /** + * @brief Sets all game filters at once. + */ void setGameFilters(bool _hideBuddiesOnlyGames, bool _hideIgnoredUserGames, bool _hideFullGames, @@ -174,7 +228,7 @@ public: bool _hideNotBuddyCreatedGames, bool _hideOpenDecklistGames, const QString &_gameNameFilter, - const QString &_creatorNameFilter, + const QStringList &_creatorNameFilter, const QSet &_gameTypeFilter, int _maxPlayersFilterMin, int _maxPlayersFilterMax, @@ -184,11 +238,123 @@ public: bool _showOnlyIfSpectatorsCanChat, bool _showOnlyIfSpectatorsCanSeeHands); + // Individual setters + void setHideBuddiesOnlyGames(bool value) + { + hideBuddiesOnlyGames = value; + refresh(); + } + void setHideIgnoredUserGames(bool value) + { + hideIgnoredUserGames = value; + refresh(); + } + void setHideFullGames(bool value) + { + hideFullGames = value; + refresh(); + } + void setHideGamesThatStarted(bool value) + { + hideGamesThatStarted = value; + refresh(); + } + void setHidePasswordProtectedGames(bool value) + { + hidePasswordProtectedGames = value; + refresh(); + } + void setHideNotBuddyCreatedGames(bool value) + { + hideNotBuddyCreatedGames = value; + refresh(); + } + void setHideOpenDecklistGames(bool value) + { + hideOpenDecklistGames = value; + refresh(); + } + void setGameNameFilter(const QString &value) + { + gameNameFilter = value; + refresh(); + } + void setCreatorNameFilters(const QStringList &values) + { + creatorNameFilters = values; + refresh(); + } + void setGameTypeFilter(const QSet &value) + { + gameTypeFilter = value; + refresh(); + } + void setMaxPlayersFilterMin(int value) + { + maxPlayersFilterMin = value; + refresh(); + } + void setMaxPlayersFilterMax(int value) + { + maxPlayersFilterMax = value; + refresh(); + } + void setMaxGameAge(const QTime &value) + { + maxGameAge = value; + refresh(); + } + void setShowOnlyIfSpectatorsCanWatch(bool value) + { + showOnlyIfSpectatorsCanWatch = value; + refresh(); + } + void setShowSpectatorPasswordProtected(bool value) + { + showSpectatorPasswordProtected = value; + refresh(); + } + void setShowOnlyIfSpectatorsCanChat(bool value) + { + showOnlyIfSpectatorsCanChat = value; + refresh(); + } + void setShowOnlyIfSpectatorsCanSeeHands(bool value) + { + showOnlyIfSpectatorsCanSeeHands = value; + refresh(); + } + + /** + * @brief Returns the number of games filtered out by the current filter. + */ int getNumFilteredGames() const; + + /** + * @brief Resets all filter parameters to default values. + */ void resetFilterParameters(); + + /** + * @brief Returns true if all filter parameters are set to their defaults. + */ bool areFilterParametersSetToDefaults() const; + + /** + * @brief Loads filter parameters from persistent settings. + * @param allGameTypes Mapping of all game types by room ID. + */ void loadFilterParameters(const QMap &allGameTypes); + + /** + * @brief Saves filter parameters to persistent settings. + * @param allGameTypes Mapping of all game types by room ID. + */ void saveFilterParameters(const QMap &allGameTypes); + + /** + * @brief Refreshes the proxy model (re-applies filters). + */ void refresh(); protected: diff --git a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp index e8ead5a02..65e1e4e71 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp @@ -510,32 +510,33 @@ void AbstractTabDeckEditor::actEditDeckInClipboardRaw() /** @brief Saves deck to clipboard with set info and annotation. */ void AbstractTabDeckEditor::actSaveDeckToClipboard() { - getDeckLoader()->saveToClipboard(true, true); + DeckLoader::saveToClipboard(getDeckList(), true, true); } /** @brief Saves deck to clipboard with annotation, without set info. */ void AbstractTabDeckEditor::actSaveDeckToClipboardNoSetInfo() { - getDeckLoader()->saveToClipboard(true, false); + DeckLoader::saveToClipboard(getDeckList(), true, false); } /** @brief Saves deck to clipboard without annotations, with set info. */ void AbstractTabDeckEditor::actSaveDeckToClipboardRaw() { - getDeckLoader()->saveToClipboard(false, true); + DeckLoader::saveToClipboard(getDeckList(), false, true); } /** @brief Saves deck to clipboard without annotations or set info. */ void AbstractTabDeckEditor::actSaveDeckToClipboardRawNoSetInfo() { - getDeckLoader()->saveToClipboard(false, false); + DeckLoader::saveToClipboard(getDeckList(), false, false); } /** @brief Prints the deck using a QPrintPreviewDialog. */ void AbstractTabDeckEditor::actPrintDeck() { auto *dlg = new QPrintPreviewDialog(this); - connect(dlg, &QPrintPreviewDialog::paintRequested, deckDockWidget->getDeckLoader(), &DeckLoader::printDeckList); + connect(dlg, &QPrintPreviewDialog::paintRequested, this, + [this](QPrinter *printer) { DeckLoader::printDeckList(printer, getDeckList()); }); dlg->exec(); } @@ -569,7 +570,7 @@ void AbstractTabDeckEditor::actLoadDeckFromWebsite() void AbstractTabDeckEditor::exportToDecklistWebsite(DeckLoader::DecklistWebsite website) { if (DeckLoader *const deck = getDeckLoader()) { - QString decklistUrlString = deck->exportDeckToDecklist(website); + QString decklistUrlString = deck->exportDeckToDecklist(getDeckList(), website); // Check to make sure the string isn't empty. if (decklistUrlString.isEmpty()) { // Show an error if the deck is empty, and return. diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp index df401e247..8b2bec10b 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp @@ -326,13 +326,13 @@ QMenu *DeckPreviewWidget::createRightClickMenu() auto saveToClipboardMenu = menu->addMenu(tr("Save Deck to Clipboard")); connect(saveToClipboardMenu->addAction(tr("Annotated")), &QAction::triggered, this, - [this] { deckLoader->saveToClipboard(true, true); }); + [this] { DeckLoader::saveToClipboard(deckLoader->getDeckList(), true, true); }); connect(saveToClipboardMenu->addAction(tr("Annotated (No set info)")), &QAction::triggered, this, - [this] { deckLoader->saveToClipboard(true, false); }); + [this] { DeckLoader::saveToClipboard(deckLoader->getDeckList(), true, false); }); connect(saveToClipboardMenu->addAction(tr("Not Annotated")), &QAction::triggered, this, - [this] { deckLoader->saveToClipboard(false, true); }); + [this] { DeckLoader::saveToClipboard(deckLoader->getDeckList(), false, true); }); connect(saveToClipboardMenu->addAction(tr("Not Annotated (No set info)")), &QAction::triggered, this, - [this] { deckLoader->saveToClipboard(false, false); }); + [this] { DeckLoader::saveToClipboard(deckLoader->getDeckList(), false, false); }); menu->addSeparator(); diff --git a/doc/doxygen-extra-pages/user_documentation/deck_management/editing_decks.md b/doc/doxygen-extra-pages/user_documentation/deck_management/editing_decks.md index d8b96dac3..030811306 100644 --- a/doc/doxygen-extra-pages/user_documentation/deck_management/editing_decks.md +++ b/doc/doxygen-extra-pages/user_documentation/deck_management/editing_decks.md @@ -2,4 +2,6 @@ @subpage editing_decks_classic -@subpage editing_decks_visual \ No newline at end of file +@subpage editing_decks_visual + +@subpage editing_decks_printings \ No newline at end of file diff --git a/doc/doxygen-extra-pages/user_documentation/deck_management/editing_decks_printings.md b/doc/doxygen-extra-pages/user_documentation/deck_management/editing_decks_printings.md index 27ade3f63..737575ae5 100644 --- a/doc/doxygen-extra-pages/user_documentation/deck_management/editing_decks_printings.md +++ b/doc/doxygen-extra-pages/user_documentation/deck_management/editing_decks_printings.md @@ -1 +1,122 @@ -@page editing_decks_printings Printing Selector \ No newline at end of file +@page editing_decks_printings Printing Selector + +\image html printing_selector.png + +# Purpose + +The PrintingSelector allows editing the PrintingInfo associated with CardInfo%s contained in DeckList%s. + +As described in PrintingInfo, a CardInfo can have different variations contained within the same or different CardSet%s. + +PrintingSelector allows the user to choose a variation, as well as pinning a variation to be used as the preferred +printing in all cases where the 'default' or 'most preferred' image should be shown. + +# Pre-requisites (User Interface) + +To use the printing selector, ensure that you are using the Post-ProviderID change behavior (Checkbox as shown in the +image). + +\image html printing_selector_pre_providerid.png + +\attention This is the default behavior, unless you have explicitly changed it, in which case you will have seen a +warning as follows. Once again, checking this box will *disable* the new behavior and revert you to the legacy system +without the printing selector. + +Disabling the printing selector: + +\image html printing_selector_enable.png + +Enabling the printing selector: + +\image html printing_selector_disable.png + +# Pre-requisites (Card Database) + +The PrintingSelector requires each underlying CardInfo object to have a entry with a *unique* providerID attribute +for each variation that should be displayed. + +Here is an example, that first defines three sets, and then the 'Ace of Hearts' card with three variations. + +```xml +PKR +STD +NBR +``` + +```xml + + + + + PKR + Cockatrice Poker + Custom + 2025-11-15 + + + STD + Standard Poker + core + 2025-11-14 + + + NBR + Numbers + core + 2025-11-14 + + + + + Ace of Hearts + Ace of Hearts + + normal + front + Ace + Ace + 14 + 14 + H + H + legal + + Joker + 0 + 1 + 0 + 0 + PKR + STD + NBR + + + +``` + +# Using the Printing Selector + +To use the PrintingSelector, first select a card in the DeckEditorDeckDockWidget: + +\image html deck_dock_deck_list.png + +You may add a variation to the mainboard by clicking the + and - buttons in the top row. The bottom row will add the +variation to your sideboard. + +\image html printing_selector.png + +You can also right-click a variation to pin it, which will move it to the top of the variation list for easy access and +additionally, use it as the default printing whenever the printing isn't explicitly specified (such as adding a card +from the database in the deck editor). + +Only one printing may be pinned at a time. + +Additionally, the PrintingSelector offers a navigation bar at the bottom to navigate through cards in your deck, as well +as an option to modify the variations of cards in bulk via the 'Bulk Selection' option. + +\image html printing_selector_navigation.png + +The search bar at the top may be used to filter the displayed variations and the cogwheel may be clicked to open up +additional settings. + +\image html printing_selector_options.png diff --git a/doc/doxygen-images/printing_selector.png b/doc/doxygen-images/printing_selector.png new file mode 100644 index 000000000..c06ac90bc Binary files /dev/null and b/doc/doxygen-images/printing_selector.png differ diff --git a/doc/doxygen-images/printing_selector_disable.png b/doc/doxygen-images/printing_selector_disable.png new file mode 100644 index 000000000..8bdd8f292 Binary files /dev/null and b/doc/doxygen-images/printing_selector_disable.png differ diff --git a/doc/doxygen-images/printing_selector_enable.png b/doc/doxygen-images/printing_selector_enable.png new file mode 100644 index 000000000..2c7d05afa Binary files /dev/null and b/doc/doxygen-images/printing_selector_enable.png differ diff --git a/doc/doxygen-images/printing_selector_navigation.png b/doc/doxygen-images/printing_selector_navigation.png new file mode 100644 index 000000000..a91ed4511 Binary files /dev/null and b/doc/doxygen-images/printing_selector_navigation.png differ diff --git a/doc/doxygen-images/printing_selector_options.png b/doc/doxygen-images/printing_selector_options.png new file mode 100644 index 000000000..2079b70ac Binary files /dev/null and b/doc/doxygen-images/printing_selector_options.png differ diff --git a/doc/doxygen-images/printing_selector_pre_providerid.png b/doc/doxygen-images/printing_selector_pre_providerid.png new file mode 100644 index 000000000..a1be993ee Binary files /dev/null and b/doc/doxygen-images/printing_selector_pre_providerid.png differ diff --git a/DoxygenLayout.xml b/doc/doxygen/DoxygenLayout.xml similarity index 100% rename from DoxygenLayout.xml rename to doc/doxygen/DoxygenLayout.xml diff --git a/libcockatrice_card/libcockatrice/card/card_info.cpp b/libcockatrice_card/libcockatrice/card/card_info.cpp index 4f1280d74..58aa83848 100644 --- a/libcockatrice_card/libcockatrice/card/card_info.cpp +++ b/libcockatrice_card/libcockatrice/card/card_info.cpp @@ -26,13 +26,9 @@ CardInfo::CardInfo(const QString &_name, const QList &_relatedCards, const QList &_reverseRelatedCards, SetToPrintingsMap _sets, - bool _cipt, - bool _landscapeOrientation, - int _tableRow, - bool _upsideDownArt) + const UiAttributes _uiAttributes) : name(_name), text(_text), isToken(_isToken), properties(std::move(_properties)), relatedCards(_relatedCards), - reverseRelatedCards(_reverseRelatedCards), setsToPrintings(std::move(_sets)), cipt(_cipt), - landscapeOrientation(_landscapeOrientation), tableRow(_tableRow), upsideDownArt(_upsideDownArt) + reverseRelatedCards(_reverseRelatedCards), setsToPrintings(std::move(_sets)), uiAttributes(_uiAttributes) { simpleName = CardInfo::simplifyName(name); @@ -41,8 +37,7 @@ CardInfo::CardInfo(const QString &_name, CardInfoPtr CardInfo::newInstance(const QString &_name) { - return newInstance(_name, QString(), false, QVariantHash(), QList(), QList(), - SetToPrintingsMap(), false, false, 0, false); + return newInstance(_name, "", false, {}, {}, {}, {}, {}); } CardInfoPtr CardInfo::newInstance(const QString &_name, @@ -52,13 +47,10 @@ CardInfoPtr CardInfo::newInstance(const QString &_name, const QList &_relatedCards, const QList &_reverseRelatedCards, SetToPrintingsMap _sets, - bool _cipt, - bool _landscapeOrientation, - int _tableRow, - bool _upsideDownArt) + const UiAttributes _uiAttributes) { CardInfoPtr ptr(new CardInfo(_name, _text, _isToken, std::move(_properties), _relatedCards, _reverseRelatedCards, - _sets, _cipt, _landscapeOrientation, _tableRow, _upsideDownArt)); + _sets, _uiAttributes)); ptr->setSmartPointer(ptr); for (const auto &printings : _sets) { diff --git a/libcockatrice_card/libcockatrice/card/card_info.h b/libcockatrice_card/libcockatrice/card/card_info.h index b72d1fbbe..a52c0553a 100644 --- a/libcockatrice_card/libcockatrice/card/card_info.h +++ b/libcockatrice_card/libcockatrice/card/card_info.h @@ -47,6 +47,21 @@ class CardInfo : public QObject { Q_OBJECT +public: + /** + * @class CardInfo::UiAttributes + * @ingroup Cards + * + * @brief Attributes of the card that affect display and game logic. + */ + struct UiAttributes + { + bool cipt = false; ///< Positioning flag used by UI. + bool landscapeOrientation = false; ///< Orientation flag for rendering. + int tableRow = 0; ///< Row index in a table or visual representation. + bool upsideDownArt = false; ///< Whether artwork is flipped for visual purposes. + }; + private: /** @name Private Card Properties * @anchor PrivateCardProperties @@ -63,10 +78,7 @@ private: QList reverseRelatedCardsToMe; ///< Cards that consider this card as related. SetToPrintingsMap setsToPrintings; ///< Mapping from set names to printing variations. QString setsNames; ///< Cached, human-readable list of set names. - bool cipt; ///< Positioning flag used by UI. - bool landscapeOrientation; ///< Orientation flag for rendering. - int tableRow; ///< Row index in a table or visual representation. - bool upsideDownArt; ///< Whether artwork is flipped for visual purposes. + UiAttributes uiAttributes; ///< Attributes that affect display and game logic ///@} public: @@ -80,10 +92,7 @@ public: * @param _relatedCards Forward references to related cards. * @param _reverseRelatedCards Backward references to related cards. * @param _sets Map of set names to printing information. - * @param _cipt UI positioning flag. - * @param _landscapeOrientation UI rendering orientation. - * @param _tableRow Row index for table placement. - * @param _upsideDownArt Whether the artwork should be displayed upside down. + * @param _uiAttributes Attributes that affect display and game logic */ explicit CardInfo(const QString &_name, const QString &_text, @@ -92,10 +101,7 @@ public: const QList &_relatedCards, const QList &_reverseRelatedCards, SetToPrintingsMap _sets, - bool _cipt, - bool _landscapeOrientation, - int _tableRow, - bool _upsideDownArt); + UiAttributes _uiAttributes); /** * @brief Copy constructor for CardInfo. @@ -108,8 +114,7 @@ public: : QObject(other.parent()), name(other.name), simpleName(other.simpleName), text(other.text), isToken(other.isToken), properties(other.properties), relatedCards(other.relatedCards), reverseRelatedCards(other.reverseRelatedCards), reverseRelatedCardsToMe(other.reverseRelatedCardsToMe), - setsToPrintings(other.setsToPrintings), setsNames(other.setsNames), cipt(other.cipt), - landscapeOrientation(other.landscapeOrientation), tableRow(other.tableRow), upsideDownArt(other.upsideDownArt) + setsToPrintings(other.setsToPrintings), setsNames(other.setsNames), uiAttributes(other.uiAttributes) { } @@ -133,10 +138,7 @@ public: * @param _relatedCards Forward relationships. * @param _reverseRelatedCards Reverse relationships. * @param _sets Printing information per set. - * @param _cipt UI positioning flag. - * @param _landscapeOrientation UI rendering orientation. - * @param _tableRow Row index for table placement. - * @param _upsideDownArt Artwork orientation flag. + * @param _uiAttributes Attributes that affect display and game logic * @return Shared pointer to the new CardInfo instance. */ static CardInfoPtr newInstance(const QString &_name, @@ -146,10 +148,7 @@ public: const QList &_relatedCards, const QList &_reverseRelatedCards, SetToPrintingsMap _sets, - bool _cipt, - bool _landscapeOrientation, - int _tableRow, - bool _upsideDownArt); + UiAttributes _uiAttributes); /** * @brief Clones the current CardInfo instance. @@ -254,29 +253,14 @@ public: //@} /** @name UI Positioning */ //@{ - bool getCipt() const + const UiAttributes &getUiAttributes() const { - return cipt; + return uiAttributes; } - bool getLandscapeOrientation() const - { - return landscapeOrientation; - } - int getTableRow() const - { - return tableRow; - } - void setTableRow(int _tableRow) - { - tableRow = _tableRow; - } - bool getUpsideDownArt() const - { - return upsideDownArt; - } - const QChar getColorChar() const; //@} + const QChar getColorChar() const; + /** @name Legacy/Convenience Property Accessors */ //@{ const QString getCardType() const; void setCardType(const QString &value); diff --git a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.cpp b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.cpp index c56f24480..35e2f3d83 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.cpp +++ b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.cpp @@ -282,9 +282,13 @@ void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml) } properties.insert("colors", colors); - CardInfoPtr newCard = - CardInfo::newInstance(name, text, isToken, properties, relatedCards, reverseRelatedCards, _sets, cipt, - landscapeOrientation, tableRow, upsideDown); + + CardInfo::UiAttributes attributes = {.cipt = cipt, + .landscapeOrientation = landscapeOrientation, + .tableRow = tableRow, + .upsideDownArt = upsideDown}; + CardInfoPtr newCard = CardInfo::newInstance(name, text, isToken, properties, relatedCards, + reverseRelatedCards, _sets, attributes); emit addCard(newCard); } } @@ -417,14 +421,15 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in } // positioning - xml.writeTextElement("tablerow", QString::number(info->getTableRow())); - if (info->getCipt()) { + const CardInfo::UiAttributes &attributes = info->getUiAttributes(); + xml.writeTextElement("tablerow", QString::number(attributes.tableRow)); + if (attributes.cipt) { xml.writeTextElement("cipt", "1"); } - if (info->getLandscapeOrientation()) { + if (attributes.landscapeOrientation) { xml.writeTextElement("landscapeOrientation", "1"); } - if (info->getUpsideDownArt()) { + if (attributes.upsideDownArt) { xml.writeTextElement("upsidedown", "1"); } 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 92525d6e1..ab4c8002d 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp +++ b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp @@ -262,9 +262,12 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml) continue; } - CardInfoPtr newCard = - CardInfo::newInstance(name, text, isToken, properties, relatedCards, reverseRelatedCards, _sets, cipt, - landscapeOrientation, tableRow, upsideDown); + CardInfo::UiAttributes attributes = {.cipt = cipt, + .landscapeOrientation = landscapeOrientation, + .tableRow = tableRow, + .upsideDownArt = upsideDown}; + CardInfoPtr newCard = CardInfo::newInstance(name, text, isToken, properties, relatedCards, + reverseRelatedCards, _sets, attributes); emit addCard(newCard); } } @@ -379,14 +382,15 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in } // positioning - xml.writeTextElement("tablerow", QString::number(info->getTableRow())); - if (info->getCipt()) { + const CardInfo::UiAttributes &attributes = info->getUiAttributes(); + xml.writeTextElement("tablerow", QString::number(attributes.tableRow)); + if (attributes.cipt) { xml.writeTextElement("cipt", "1"); } - if (info->getLandscapeOrientation()) { + if (attributes.landscapeOrientation) { xml.writeTextElement("landscapeOrientation", "1"); } - if (info->getUpsideDownArt()) { + if (attributes.upsideDownArt) { xml.writeTextElement("upsidedown", "1"); } diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp index 43a474ca8..a12c6fe0e 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp @@ -722,7 +722,7 @@ void DeckList::refreshDeckHash() /** * Calls a given function on each card in the deck. */ -void DeckList::forEachCard(const std::function &func) +void DeckList::forEachCard(const std::function &func) const { // Support for this is only possible if the internal structure // doesn't get more complicated. diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h index 2a03c4374..b724efc0f 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h @@ -315,7 +315,7 @@ public: * * @param func Function taking (zone node, card node). */ - void forEachCard(const std::function &func); + void forEachCard(const std::function &func) const; }; #endif diff --git a/libcockatrice_settings/libcockatrice/settings/game_filters_settings.cpp b/libcockatrice_settings/libcockatrice/settings/game_filters_settings.cpp index 2da9b9bc3..e5db3010d 100644 --- a/libcockatrice_settings/libcockatrice/settings/game_filters_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/game_filters_settings.cpp @@ -25,7 +25,7 @@ void GameFiltersSettings::setHideBuddiesOnlyGames(bool hide) bool GameFiltersSettings::isHideBuddiesOnlyGames() { QVariant previous = getValue("hide_buddies_only_games"); - return previous == QVariant() || previous.toBool(); + return previous == QVariant() ? false : previous.toBool(); } void GameFiltersSettings::setHideFullGames(bool hide) @@ -36,7 +36,7 @@ void GameFiltersSettings::setHideFullGames(bool hide) bool GameFiltersSettings::isHideFullGames() { QVariant previous = getValue("hide_full_games"); - return !(previous == QVariant()) && previous.toBool(); + return previous == QVariant() ? false : previous.toBool(); } void GameFiltersSettings::setHideGamesThatStarted(bool hide) @@ -47,7 +47,7 @@ void GameFiltersSettings::setHideGamesThatStarted(bool hide) bool GameFiltersSettings::isHideGamesThatStarted() { QVariant previous = getValue("hide_games_that_started"); - return !(previous == QVariant()) && previous.toBool(); + return previous == QVariant() ? false : previous.toBool(); } void GameFiltersSettings::setHidePasswordProtectedGames(bool hide) @@ -58,7 +58,7 @@ void GameFiltersSettings::setHidePasswordProtectedGames(bool hide) bool GameFiltersSettings::isHidePasswordProtectedGames() { QVariant previous = getValue("hide_password_protected_games"); - return previous == QVariant() || previous.toBool(); + return previous == QVariant() ? false : previous.toBool(); } void GameFiltersSettings::setHideIgnoredUserGames(bool hide) @@ -69,7 +69,7 @@ void GameFiltersSettings::setHideIgnoredUserGames(bool hide) bool GameFiltersSettings::isHideIgnoredUserGames() { QVariant previous = getValue("hide_ignored_user_games"); - return !(previous == QVariant()) && previous.toBool(); + return previous == QVariant() ? true : previous.toBool(); } void GameFiltersSettings::setHideNotBuddyCreatedGames(bool hide) @@ -80,7 +80,7 @@ void GameFiltersSettings::setHideNotBuddyCreatedGames(bool hide) bool GameFiltersSettings::isHideNotBuddyCreatedGames() { QVariant previous = getValue("hide_not_buddy_created_games"); - return !(previous == QVariant()) && previous.toBool(); + return previous == QVariant() ? false : previous.toBool(); } void GameFiltersSettings::setHideOpenDecklistGames(bool hide) @@ -91,7 +91,7 @@ void GameFiltersSettings::setHideOpenDecklistGames(bool hide) bool GameFiltersSettings::isHideOpenDecklistGames() { QVariant previous = getValue("hide_open_decklist_games"); - return !(previous == QVariant()) && previous.toBool(); + return previous == QVariant() ? false : previous.toBool(); } void GameFiltersSettings::setGameNameFilter(QString gameName) @@ -104,14 +104,14 @@ QString GameFiltersSettings::getGameNameFilter() return getValue("game_name_filter").toString(); } -void GameFiltersSettings::setCreatorNameFilter(QString creatorName) +void GameFiltersSettings::setCreatorNameFilters(QStringList creatorName) { setValue(creatorName, "creator_name_filter"); } -QString GameFiltersSettings::getCreatorNameFilter() +QStringList GameFiltersSettings::getCreatorNameFilters() { - return getValue("creator_name_filter").toString(); + return getValue("creator_name_filter").toStringList(); } void GameFiltersSettings::setMinPlayers(int min) @@ -182,7 +182,7 @@ void GameFiltersSettings::setShowSpectatorPasswordProtected(bool show) bool GameFiltersSettings::isShowSpectatorPasswordProtected() { QVariant previous = getValue("show_spectator_password_protected"); - return previous == QVariant() ? true : previous.toBool(); + return previous == QVariant() ? false : previous.toBool(); } void GameFiltersSettings::setShowOnlyIfSpectatorsCanChat(bool show) @@ -193,7 +193,7 @@ void GameFiltersSettings::setShowOnlyIfSpectatorsCanChat(bool show) bool GameFiltersSettings::isShowOnlyIfSpectatorsCanChat() { QVariant previous = getValue("show_only_if_spectators_can_chat"); - return previous == QVariant() ? true : previous.toBool(); + return previous == QVariant() ? false : previous.toBool(); } void GameFiltersSettings::setShowOnlyIfSpectatorsCanSeeHands(bool show) @@ -204,5 +204,5 @@ void GameFiltersSettings::setShowOnlyIfSpectatorsCanSeeHands(bool show) bool GameFiltersSettings::isShowOnlyIfSpectatorsCanSeeHands() { QVariant previous = getValue("show_only_if_spectators_can_see_hands"); - return previous == QVariant() ? true : previous.toBool(); + return previous == QVariant() ? false : previous.toBool(); } \ No newline at end of file diff --git a/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h b/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h index ef2d802fd..d62fea03d 100644 --- a/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h @@ -24,7 +24,7 @@ public: bool isHideNotBuddyCreatedGames(); bool isHideOpenDecklistGames(); QString getGameNameFilter(); - QString getCreatorNameFilter(); + QStringList getCreatorNameFilters(); int getMinPlayers(); int getMaxPlayers(); QTime getMaxGameAge(); @@ -42,7 +42,7 @@ public: void setHidePasswordProtectedGames(bool hide); void setHideNotBuddyCreatedGames(bool hide); void setGameNameFilter(QString gameName); - void setCreatorNameFilter(QString creatorName); + void setCreatorNameFilters(QStringList creatorName); void setMinPlayers(int min); void setMaxPlayers(int max); void setMaxGameAge(const QTime &maxGameAge); diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index 049dbaede..41335d72d 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -204,11 +204,11 @@ CardInfoPtr OracleImporter::addCard(QString name, bool upsideDown = layout == "flip" && side == "back"; // insert the card and its properties - QList reverseRelatedCards; SetToPrintingsMap setsInfo; setsInfo[printingInfo.getSet()->getShortName()].append(printingInfo); - CardInfoPtr newCard = CardInfo::newInstance(name, text, isToken, properties, relatedCards, reverseRelatedCards, - setsInfo, cipt, landscapeOrientation, tableRow, upsideDown); + CardInfo::UiAttributes attributes = {cipt, landscapeOrientation, tableRow, upsideDown}; + CardInfoPtr newCard = + CardInfo::newInstance(name, text, isToken, properties, relatedCards, {}, setsInfo, attributes); if (name.isEmpty()) { qDebug() << "warning: an empty card was added to set" << printingInfo.getSet()->getShortName(); diff --git a/webclient/prebuild.js b/webclient/prebuild.js index f931f508b..3b420f32c 100644 --- a/webclient/prebuild.js +++ b/webclient/prebuild.js @@ -12,7 +12,7 @@ const serverPropsFile = `${ROOT_DIR}/server-props.json`; const masterProtoFile = `${ROOT_DIR}/proto-files.json`; const sharedFiles = [ - ['../common/pb', protoFilesDir], + ['../libcockatrice_protocol/libcockatrice/protocol/pb', protoFilesDir], ['../cockatrice/resources/countries', `${ROOT_DIR}/images/countries`], ];